在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
以下内容摘自: http://www.cnblogs.com/r01cn/archive/2011/12/04/2275208.html
http://www.cnblogs.com/r01cn/archive/2011/12/06/2276733.html
感谢作者的翻译,这里只是译文。原书名:Pro ASP.NET MVC 3 Framework
第十二章 控制器与动作 PART2 产生输出 控制器在完成了一个请求的处理之后,它通常需要生成一个响应。通过实现IController接口,我们直接生成的是祼机控制器(意即很原始的控制器,或者叫祼态控制器,或干脆叫做裸控制器更好些? — 译者注),我们需要负责处理一个请求的各个方面,包括生成对客户端的响应。例如,如果我们想要发送一个HTML响应,那么我们必须生成并装配HTML的数据,然后用Response.Write方法把它发送到客户端。类似地,如果我们想把用户的浏览器重定向到另一个URL,我们需要调用Response.Redirect方法,并直接传递我们感兴趣的URL。清单12-7对这两种办法都作了演示。 Listing 12-7. Generating Results in an IController Implementation
using System.Web.Mvc; using System.Web.Routing; namespace ControllersAndActions.Controllers { public class BasicController : IController { public void Execute(RequestContext requestContext) { string controller = (string)requestContext.RouteData.Values["controller"]; string action = (string)requestContext.RouteData.Values["action"]; requestContext.HttpContext.Response.Write( string.Format("Controller: {0}, Action: {1}", controller, action)); // ... or ... requestContext.HttpContext.Response.Redirect("/Some/Other/Url"); } } } 当通过Controller类派生控制器时,你可以使用同样的办法。上述在Execute方法中读取requestContext.HttpContext.Response属性时返回的是HttpResponseBase类,这个类在我们的派生控制器中可以直接通过Controller.Response属性进行使用,如清单12-8所示。 Listing 12-8. Using the Response Property to Generate Output
using System.Web.Mvc; namespace ControllersAndActions.Controllers { public class DerivedController : Controller { public void Index() { string controller = (string)RouteData.Values["controller"]; string action = (string)RouteData.Values["action"]; Response.Write( string.Format("Controller: {0}, Action: {1}", controller, action)); // ... or ... Response.Redirect("/Some/Other/Url"); } } } 这种办法是可以工作的,但它有几个问题: · The controller classes must contain details of HTML or URL structure, which makes the classes harder to read and maintain. · 把响应直接生成为输出的控制器难以进行单元测试。为了确定输出表示的是什么,你需要生成Response对象的模仿实现,然后才能处理你从控制器接收到的输出。例如,这可能意味着要解析HTML关键词,这是费时而痛苦的过程。 · 这种精细处理每个响应细节的方式是乏味而易错的。有些程序员喜欢对建立原始控制器的过程进行绝对控制,但大多数人很快就失败了。 幸运的是,MVC框架一个叫做动作结果的很好的特性解决了所有这些问题。以下小节介绍这个动作结果概念,并向你演示可以用它来生成控制器响应的不同方法。 MVC框架通过使用动作结果把指明(stating)意图与执行(executing)意图分离开来。它工作起来十分简单。 注意:动作结果系统是一种命令模式。这个模式描述你所处的场景并发送一些对象,这些对象描述了要执行的操作。更多细节参阅http://en.wikipedia.org/wiki/Command_pattern。 根据这一定义,我们可以这样来理解动作结果:动作结果是一种命令,它的目的是要执行一个方法。为了实现这种要执行一个方法的命令,我们需要形成一个对象,用这个对象来封装一些信息,这些信息包括目标方法名、包含该目标方法的对象、该目标方法所需要的参数等。 当MVC框架从一个动作方法接收一个ActionResult对象时,MVC框架调用由这个类所定义的ExecuteResult方法。然后该动作结果的实现为你处理这个Response对象,生成符合你意图的输出。一个简单的例子是清单12-9所示的RedirectResult类。MVC框架开源的好处之一是你可以看到场景背后的事情是如何进行的。我们简化了这个类,以使它易于阅读。 Listing 12-9. The System.Web.Mvc.RedirectResult Class
public class RedirectResult : ActionResult { public RedirectResult(string url) : this(url, permanent: false) { } public RedirectResult(string url, bool permanent) { Permanent = permanent; Url = url; } public bool Permanent { get; private set; } public string Url { get; private set; } public override void ExecuteResult(ControllerContext context) { string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext); if (Permanent) { context.HttpContext.Response.RedirectPermanent(destinationUrl, endResponse: false); } else { context.HttpContext.Response.Redirect(destinationUrl, endResponse: false); } } } 当我们生成RedirectResult类的实例时,我们在其中传递了重定向用户的URL,以及(可选地)是否是永久还是临时重定向。ExecuteResult方法,它将在我们的动作方法完成时由MVC框架执行,通过框架提供的ControllerContext对象得到这个查询的Response对象,并调用RedirectPermanent或Redirect方法,这是我们在清单12-8中明确用手工做的事情。 UNIT TESTING CONTROLLERS AND ACTIONS · 你可以在一个web服务器之外测试控制器与动作。通过它们的基类(如HttpRequestBase)访问上下文对象,这易于模仿。 · 你不需要解析任何HTML来测试一个动作方法的结果。你可以检查所返回的ActionResult对象,以确保你接收了预期的结果。 · 你不需要模拟客户端请求。MVC框架的模型绑定系统允许你编写以方法参数接收输入的动作方法。要测试一个动作方法,你只要简单地直接调用该动作方法,并提供你感兴趣的参数值。 随着本书的进行,我们将向你演示如何生成各种动作结果的单元测试。 Listing 12-10. Using the RedirectResult Class
using System.Web.Mvc; namespace ControllersAndActions.Controllers { public class DerivedController : Controller { public void Index() { string controller = (string)RouteData.Values["controller"]; string action = (string)RouteData.Values["action"]; Response.Write( string.Format("Controller: {0}, Action: {1}", controller, action)); } public ActionResult Redirect() { return new RedirectResult("/Derived/Index"); } } } 如果你启动这个应用程序,并导航到/Derived/Redirect,你的浏览器将被重定向到/Derived/Index。为了使你的代码更简单,Controller类包含了用来生成各种ActionResults的一些便携方法。于是,例如通过返回Redirect方法的结果,我们可以取得清单12-10所示的效果,如清单12-11所示。 Listing 12-11. Using a Controller Convenience Method for Creating an Action Result
public ActionResult Redirect() { return Redirect("/Derived/Index"); } 在动作结果系统中再没有其它复杂的东西了,你最终实现了更简单、更清晰、且更具一致性的代码。而且,你可以很容易地测试你的动作方法。例如,在这个重定向案例中,你可以简单地检查该动作方法返回了一个RedirectResult实例,以及Url属性含有你预期的目标。
在以下小节中,我们将向你演示如何使用这些结果,以及如何生成和使用自定义动作结果。 通过渲染视图返回HTML 一个动作方法最常用的一种响应形式是生成HTML并把它发送给浏览器。当使用动作结果系统时,你通过生成一个ViewResult类的实例来做这件事,该实例指定你想渲染以生成这个HTML视图,当清单12-12所示。 Listing 12-12. Specifying a View to Be Rendered Using ViewResult
using System.Web.Mvc; namespace ControllersAndActions.Controllers { public class ExampleController : Controller { public ViewResult Index() { return View("Homepage"); } } } 在这个清单中,我们使用View辅助方法来生成ViewResult类的一个实例,然后它作为该动作方法的结果被返回。 · /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.aspx · /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.ascx · /Areas/<AreaName>/Views/Shared/<ViewName>.aspx · /Areas/<AreaName>/Views/Shared/<ViewName>.ascx · /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.cshtml · /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.vbhtml · /Areas/<AreaName>/Views/Shared/<ViewName>.cshtml · /Areas/<AreaName>/Views/Shared/<ViewName>.vbhtml 你可以从上述列表看出,框架也查找了遗留视图引擎生成的视图(.aspx和.ascs文件扩展名),即使我们在生成该项目时指定的是Razor。框架也查找了C#和VB的.NET Razor模板(.cshtml文件为C#模板的,.vbhtml是VB的)。MVC框架依次检查这些文件是否存在。只要它定位到一个匹配,它就用这个视图来渲染该动作方法的结果。 · /Views/<ControllerName>/<ViewName>.aspx · /Views/<ControllerName>/<ViewName>.ascx · /Views/Shared/<ViewName>.aspx · /Views/Shared/<ViewName>.ascx · /Views/<ControllerName>/<ViewName>.cshtml · /Views/<ControllerName>/<ViewName>.vbhtml · /Views/Shared/<ViewName>.cshtml · /Views/Shared/<ViewName>.vbhtml 再一次地,一旦MVC框架检测一个位置并找到一个文件,搜索便停止,已经找到的这个视图便被用来渲染响应到客户端。 UNIT TEST: RENDERING A VIEW To test the view that an action method renders, you can inspect the ViewResult object that it returns. This is not quite the same thing—after all, you are not following the process through to check the final HTML that is generated—but it is close enough, as long as you have reasonable confidence that the MVC Framework view system works properly.
public ViewResult Index() { return View("Homepage"); } 通过读取ViewResult对象的ViewName属性,你可以确定哪个视图被选中了,如以下测试方法所示: [TestMethod] public void ViewSelectionTest() { // Arrange - create the controller ExampleController target = new ExampleController(); // Act - call the action method ActionResult result = target.Index(); // Assert - check the result Assert.AreEqual("Homepage", result.ViewName); } 当你测试选择了默认视图的动作方法时,稍有不同,像这样:
public ViewResult Index() { return View(); } 在这种情况下,你需要对视图名用空字符串,像这样: Assert.AreEqual("", result.ViewName); MVC框架搜索视图目录的顺序是“约定优于配置”这一规范的另一个例子(The sequence of directories that the MVC Framework searches for a view is another example of convention over configuration)。你不需要用框架来注册你的视图文件。只需要把它们放在一组已知位置的一个位置上,框架会找到它们。 Listing 12-13. Creating a ViewResult Without Specifying a View
using System.Web.Mvc; namespace ControllersAndActions.Controllers { public class ExampleController : Controller { public ViewResult Index() { return View(); } } } 当我们这样做时,MVC框架假设我们想渲染的是一个与动作方法同名的视图。意即,清单12-13中对View方法的调用将启动对一个名字为Index的视图的搜索。 注意:其效果是一个与动作方法同名的视图被搜寻,但视图名实际上是根据RouteData.Values[“action”]的值所确定的,这就是你在清单12-7中以及在第11章路由系统部分所看到的情况。
public ViewResult Index() { return View("Index", "_AlternateLayoutPage"); } 译者注:在这个方法中,"_AlternateLayoutPage"参数给出的是布局文件名(不带扩展名),该布局应当是/View/Shared/文件夹中的_AlternateLayoutPage.cshtml文件。 SPECIFYING A VIEW BY ITS PATH
using System.Web.Mvc; namespace ControllersAndActions.Controllers { public class ExampleController : Controller { public ViewResult Index() { return View("~/Views/Other/Index.cshtml"); } } } 当你像这样指定了一个视图时,路径必须从/或~/开始,并包括文件扩展名(例如,.cshtml对应的是含有C#代码的Razor视图)。 如果你感到你要用这一特性(feature),我们建议你花一些时间,并自问一下你要达到什么结果。如果是试图渲染属于另一个控制器的视图,那么你也许最好把用户重向到那个控制器的一个动作方法(参见本章稍后“重定向到一个动作方法”小节的例子)。因为它不适合你项目的组织方式,如果你试图围绕命名方案进行工作,那么参见第15章,它解释了如何实现一个自定义搜索顺序。(这一小节的含义是你最好不要用这种办法直接指定视图,这不利于应用程序的进一步扩展和维护 — 译者注) 把数据从动作方法传递给视图 我们通常需要把数据从一个动作方法传递给一个视图。MVC框架提供了许多做这种事情的不同方式,我们将在以下小节中加以描述。在这些小节中,我们提及的关于视图的论题,将在第15章作深度讨论。在本章中,我们将只讨论足以演示我们感兴趣的控制器特性(features)所需要的视图功能性。 提供视图模型对象 你可以把一个对象作为View方法的参数,把这个对象发送给视图,如清单12-14所示。 Listing 12-14. Specifying a View Model Object
public ViewResult Index() { DateTime date = DateTime.Now; return View(date); } 我们已经传递了一个DateTime对象作为视图模型。我们可以在视图中用Razor的Model关键词来访问这个对象,如清单12-15所示。 Listing 12-15. Accessing a View Model in a Razor View @{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @(((DateTime)Model).DayOfWeek) 清单12-15所示的视图称为非类型或弱类型视图。该视图不知道关于视图模型对象的任何事情,而把它作为对象的一个实例来看待。为了得到DayOfWeek属性的值,我们需要把这个对象转换成DateTime的一个实例。这可以工作,但会产生杂乱的视图。我们可以通过生成强类型视图加以整理,在强类型视图中,我们告诉视图,视图模型对象将是什么类型,如清单12-16所示。 Listing 12-16. A Strongly Typed View @model DateTime @{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @Model.DayOfWeek 我们用Razor的model关键词指定了视图的模型类型。注意,当我们指定模型类型时,使用小写的m,而在读取值时使用大写的M。这样做不仅便于整洁我们的视图,而且Visual Studio对强类型视图支持智能感应,如图12-3所示。 图12-3. 支持强类型视图的智能感应 UNIT TEST: VIEW MODEL OBJECTS 你可以通过ViewResult.ViewData.Model属性来访问从动作方法传递给视图的视图模型对象。以下是一个简单的动作方法:
public ViewResult Index() { return View((object)"Hello, World"); }
该动作方法传递一个字符串作为视图模型对象。我们已经把它转换成object,以便编译器不会认为我们想用的是指定视图名的那个View重载。我们可以通过ViewData.Model属性来访问这个视图模型,如以下测试方法所示: [TestMethod] public void ViewSelectionTest() { // Arrange - create the controller ExampleController target = new ExampleController(); // Act - call the action method ActionResult result = target.Index(); // Assert - check the result Assert.AreEqual("Hello, World", result.ViewData.Model); } 用ViewBag传递数据 在第3章中我们介绍过View Bag(视图包)特性(feature)。该特性允许你在一个动态对象上定义任意属性,并在视图中访问它们。这个动态对象可以通过Controller.ViewBag属性进行访问,如清单12-17所示。 Listing 12-17. Using the View Bag Feature
public ViewResult Index() { ViewBag.Message = "Hello"; ViewBag.Date = DateTime.Now; return View(); } 在该清单中,我们通过简单赋值的办法,已经定义了名为Message和Date的属性。在此之前这些属性是不存在的,我们不做任何准备地生成了它们。要在视图中读回这些数据,我们简单地采用在动作方法中设置的同样的属性,如清单12-18所示。 Listing 12-18. Reading Data from the ViewBag
@{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @ViewBag.Date.DayOfWeek <p /> The message is: @ViewBag.Message
ViewBag相对于使用一个视图模型对象有一个优点:它便于把多个对象发送给视图。如果我们只能使用视图模型,那么,为了获得清单12-17和12-18的同样效果,我们就需要生成一个新类型,它具有string和DateTime成员。 当用动态对象进行工作时,你可以在视图中调用方法和属性的任意序列,像这样: The day is: @ViewBag.Date.DayOfWeek.Blah.Blah.Blah Visual Studio不能提供对任何动态对象的智能感应支持,包括ViewBag,而且在视图被渲染之前不支持诸如“对此无法展示”之类的错误提示。 提示:我们喜欢ViewBag的灵活性,但我们倾向于使用强类型视图。在同一个视图中既使用视图模型也使用View Bag是没有限制的。它们两者可以无干扰地一起工作。 UNIT TEST: VIEWBAG 你可以通过ViewResult.ViewBag属性读取ViewBag的值。以下测试方法是针对清单12-17中动作方法的测试: [TestMethod] public void ViewSelectionTest() { // Arrange - create the controller ExampleController target = new ExampleController(); // Act - call the action method ActionResult result = target.Index(); // Assert - check the result Assert.AreEqual("Hello", result.ViewBag.Message); } 用View Data传递数据 View Bag特性(feature)是随MVC的第3版引入的。在此之前,用视图模型对象的替代方案主要是View Data。View Data(视图数据)特性(feature)类似于View Bag,但它是用ViewDataDictionary类不是动态对象实现的。ViewDataDictionary类似于规则的“键/值”集合,并通过Controller类的ViewData属性进行访问,如清单12-19所示。 Listing 12-19. Setting Data in the ViewData Class
public ViewResult Index() { ViewData["Message"] = "Hello"; ViewData["Date"] = DateTime.Now; return View(); } 就像使用“键/值”集合那样,你可以在视图中读回这些数据值,如清单12-20所示。 Listing 12-20. Reading View Data in a View @{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @(((DateTime)ViewData["Date"]).DayOfWeek) <p /> The message is: @ViewData["Message"] 你可以看出,我们必须对从视图数据获得的对象进行转换,这十分类似于我们对非类型视图所做的那样。 注意:在View Bag可用的情况下,我们现在不太喜欢View Data了。 UNIT TEST: VIEW DATA 通过读取ViewResult.ViewData的值,你可以测试使用View Data的动作方法。以下测试是用于清单12-19中的动作方法的: [TestMethod] public void ViewSelectionTest() { // Arrange - create the controller ExampleController target = new ExampleController(); // Act - call the action method ActionResult result = target.Index(); // Assert - check the result Assert.AreEqual("Hello", result.ViewData["Message"]); } 执行重定向 一个动作方法的通常结果并不是直接产生输出,而是把用户的浏览器重定向到另一个URL。大多数情况下,这个URL是应用程序中的另一个动作方法,它生成你希望用户看到的输出。 POST/Redirect/GET模式 重定向最经常的应用是用在处理HTTP POST请求的动作方法中。正如我们在上一章所提到的,当你希望修改应用程序状态时,才会使用POST请求。如果你只是简单地在请求处理之后马上返回HTML,你就陷入这样的风险:用户点击浏览器的重载按钮(意指“刷新当前页面” — 译者注),并再次递交该表单、引发异常及不符需求的结果。 为了避免这种问题,你可以遵循Post/Redirect/Get模式。在这个模式中,你接收一个POST请求、处理它、然后重定向浏览器,以便由浏览器形成另一个GET请求的URL。GET请求不会修改你应用程序的状态,因此,该请求的任何不经意的再次递交都不会引起任何问题。 在执行重定向时,给浏览器发送的是以下两个HTTP代码之一: · 发送HTTP代码302,这是一个临时重定向。这是最常用的重定向类型,而且直到MVC 3,这是MVC框架内建支持的唯一的一种重定向。当使用Post/Redirect/Get模式时,这是你要发送的代码。 · 发送HTTP代码301,它表示一个永久重定向。应该小心地使用它,因为它指示HTTP代码接收器不要再次请求原始URL,并使用包含重定向代码所伴随的新URL。如果你有疑问,请使用临时重定向,即,发送代码302。 重定向到字面(Literal)URL 重定向浏览器最基本的方式是调用Redirect方法,它返回RedirectResult类的一个实例,如清单12-21所示。 Listing 12-21. Redirecting to a Literal URL
public RedirectResult Redirect() { return Redirect("/Example/Index"); } 你要重定向的URL应当表示成一个字符串,并作为传递给Redirect方法的参数。Redirect方法发送一个临时重定向。你可以用RedirectPermanent方法发送一个永久重定向,如清单12-22所示。 Listing 12-22. Permanently Redirecting to a Literal URL
public RedirectResult Redirect() { return RedirectPermanent("/Example/Index"); } 提示:如果你喜欢,你可以用Redirect方法的重载版本,它以一个布尔型参数指定是否永久重定向。 UNIT TEST: LITERAL REDIRECTIONS 字面重定向易于测试。你可以用RedirectResult类的Url和Permanen属性来读取URL以及该重定向是永久的还是临时的。以下是清单12-21重定向的一个测试方法。 [TestMethod] public void RedirectTest() { // Arrange - create the controller ExampleController target = new ExampleController(); // Act - call the action method RedirectResult result = target.Redirect(); // Assert - check the result Assert.IsFalse(result.Permanent); Assert.AreEqual("/Example/Index", result.Url); } 重定向到路由系统的一个URL 如果你要把用户重定向到应用程序的一个不同的部分,你需要确保你发送的URL符合之前章节所描述的URL模式。用字面URL进行重定向的问题是,对路由方案的任何修改,都意味着你需要检查你的代码,并对URL进行更新。 一种可选的替代办法是,你可以运用路由系统,以RedirectToRoute方法来生成有效的URL,该方法会生成RedirectToRouteResult的一个实例,如清单12-23所示。 Listing 12-23. Redirecting to a Routing System URL
public RedirectToRouteResult Redirect() { return RedirectToRoute(new { controller = "Example", action = "Index", ID = "MyID" }); } RedirectToRoute方法发布一个临时重定向。对于永久重定向,使用RedirectToRoutePermanent方法。这两个方法都以一个匿名类型为参数,其属性然后被传递给路由系统,以生成一个URL。此过程的更多细节请参阅第11章的“生成输出URL”小节。 UNIT TESTING: ROUTED REDIRECTIONS 以下是测试清单12-23动作方法的示例: [TestMethod] public void RedirectValueTest() { // Arrange - create the controller ExampleController target = new ExampleController(); // Act - call the action method RedirectToRouteResult result = target.Redirect(); // Assert - check the result Assert.IsFalse(result.Permanent); Assert.AreEqual("Example", result.RouteValues["controller"]); Assert.AreEqual("Index", result.RouteValues["action"]); Assert.AreEqual("MyID", result.RouteValues["ID"]); } 重定向到一个动作方法 通过使用RedirectToAction方法,你可以更雅致地重定向到一个动作方法。这只是RedirectToAction方法一个封装程序,让你指定动作方法和控制器的值,而不需要生成一个匿名类型,如清单12-24所示。 Listing 12-24. Redirecting Using the RedirectToAction Method
public RedirectToRouteResult Redirect() { return RedirectToAction("Index"); } 如果你只指定一个动作方法,那么它假设你指向的是当前控制器的一个动作方法。如果你想重定向到另一个控制器,你需要以参数提供其名字,像这样: return RedirectToAction("Index", "MyController"); 还有一些其它的重载版本,你可以用来为URL生成提供额外的值。这些版本是用一个匿名类型来表示的,这有点破坏了便携方法,但仍然能使你的代码易于阅读。 注意:你为控制的动作方法所提供的值,在它们被传递给路由系统之前,是不会被检验的。你有责任要确保你所指定的目标是实际存在的。 RedirectToAction方法执行一个临时重定向。用RedirectToActionPermanent进行永久重定向。 PRESERVING DATA ACROSS A REDIRECTION 重定向引发浏览器递交一个全新的HTTP请求,这意味着你没有访问原始请求的细节。如果你希望把一个请求的数据传递给下一个请求,你可以用Temp Data特性。 TempData类似于Session数据,只不过,TempData的值在被读取之后,被标记为删除,并在该请求已经被处理之后被删除。这对于你跨越重定向而保留的短期数据是一种理想的安排。以下是使用了RedirectToAction方法的一个简单的动作方法: |
请发表评论