12.3 产生输出 — 精通 MVC 3 框架

Producing Output 产生输出

After a controller has finished processing a request, it usually needs to generate a response. When we created our bare-metal controller by implementing the IController interface directly, we needed to take responsibility for every aspect of processing a request, including generating the response to the client. If we want to send an HTML response, for example, then we must create and assemble the HTML data and send it to the client using the Response.Write method. Similarly, if we want to redirect the user's browser to another URL, we need to call the Response.Redirect method and pass the URL we are interested in directly. Both of these approaches are shown in Listing 12-7. 
控制器在完成了一个请求的处理之后,它通常需要生成一个响应。通过实现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"); } } } 

You can use the same approach when you have derived your controller from the Controller class. The HttpResponseBase class that is returned when you read the requestContext.HttpContext.Response property in your Execute method is available through the Controller.Response property, as shown in Listing 12-8. 
当通过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"); } } } 

This approach works, but it has a few problems: 
这种办法是可以工作的,但它有几个问题

  • The controller classes must contain details of HTML or URL structure, which makes the classes harder to read and maintain. 
    控制器类必须含有HTML或URL结构细节,这些使类难以阅读和维护。
  • It is hard to unit test a controller that generates its response directly to the output. You need to create mock implementations of the Response object, and then be able to process the output you receive from the controller in order to determine what the output represents. This can mean parsing HTML for keywords, for example, which is a drawn-out and painful process. 
    把响应直接生成为输出的控制器难以进行单元测试。为了确定输出表示的是什么,你需要生成Response对象的模仿实现,然后才能处理你从控制器接收到的输出。例如,这可能意味着要解析HTML关键词,这是费时而痛苦的过程
  • Handling the fine detail of every response this way is tedious and error-prone. Some programmers will like the absolute control that building a raw controller gives, but normal people get frustrated pretty quickly. 
    这种精细处理每个响应细节的方式是乏味而易错的。有些程序员喜欢建立原始控制器的过程进行绝对控制,但大多数人很快就失败了。

Fortunately, the MVC Framework has a nice feature that addresses all of these issues, called action results. The following sections introduce the action result concept and show you the different ways that it can be used to generate responses from controllers. 
幸运的是,MVC框架一个叫做动作结果的很好的特性解决了所有这些问题。以下小节介绍这个动作结果概念,并向你演示可以用它来生成控制器响应的不同方法。

Understanding Action Results
理解动作结果

The MVC Framework uses action results to separate stating our intentions from executing our intentions. It works very simply. 
MVC框架通过使用动作结果把指明意图与执行意图分离开来。它工作起来十分简单。

Instead of working directly with the Response object, we return an object derived from the ActionResult class that describes what we want the response from our controller to be, such as rendering a view or redirecting to another URL or action method. 
不是直接用Response对象进行工作,而是返回派生于ActionResult类的一个对象,它描述我们希望控制器响应要做什么,例如,渲染一个视图、重定向到另一个URL或动作方法。

■ Note The system of action results is an example of the command pattern. This pattern describes scenarios where you store and pass around objects that describe operations to be performed. See http://en.wikipedia.org/wiki/Command_pattern for more details. 
注:动作结果系统是一种命令模式。这个模式描述你所处的场景并发送一些对象,这些对象描述了要执行的操作。更多细节参阅http://en.wikipedia.org/wiki/Command_pattern。

译者注:在上述网址上译者看到了命令模式定义

In object-oriented programming, the command pattern is a design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. This information includes the method name, the object that owns the method and values for the method parameters. 
面向对象编程中,命令模式是一种设计模式,在这个模式中,用一个对象来表示和封装之后要调用的方法所需要的所有信息。这些信息包括方法名、拥有这个方法的对象、以及该方法参数的值。

根据这一定义,我们可以这样来理解动作结果:动作结果是一种命令,它的目的是要执行一个方法。为了实现这种要执行一个方法的命令,我们需要形成一个对象,用这个对象来封装一些信息,这些信息包括目标方法名、包含该目标方法的对象、该目标方法所需要的参数等。

When the MVC Framework receives an ActionResult object from an action method, it calls the ExecuteResult method defined by that class. The action result implementation then deals with the Response object for you, generating the output that corresponds to your intention. A simple example is the RedirectResult class shown in Listing 12-9. One of the benefits of the MVC Framework being open source is that you can see how things work behind the scenes. We've simplified this class to make it easier to read. 
当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); } } } 

When we create an instance of the RedirectResult class, we pass in the URL we want to redirect the user to and, optionally, whether this is a permanent or temporary redirection. The ExecuteResult method, which will be executed by the MVC Framework when our action method has finished, gets the Response object for the query through the ControllerContext object that the framework provides, and calls either the RedirectPermanent or Redirect method, which is exactly what we were doing manually in Listing 12-8. 
当我们生成RedirectResult类的实例时,我们在其中传递了重定向用户的URL,以及(可选地)是否是永久还是临时重定向。ExecuteResult方法,它将在我们的动作方法完成时由MVC框架执行,通过框架提供的ControllerContext对象得到这个查询的Response对象,并调用RedirectPermanent或Redirect方法,这是我们在清单12-8中明确用手工做的事情。

UNIT TESTING CONTROLLERS AND ACTIONS
单元测试控制器与动作

Many parts of the MVC Framework are designed to facilitate unit testing, and this is especially true for actions and controllers. There are a few reasons for this support: 
MVC框架的许多部分都被设计成便于单元测试,特别是对控制器与动作。这种支持有几个原因:

  • You can test actions and controllers outside a web server. The context objects are accessed through their base classes (such as HttpRequestBase), which are easy to mock. 
    你可以在一个web服务器之外测试控制器与动作。通过它们的基类(如HttpRequestBase)访问上下文对象,这易于模仿。
  • You don't need to parse any HTML to test the result of an action method. You can inspect the ActionResult object that is returned to ensure that you received the expected result. 
    你不需要解析任何HTML来测试一个动作方法的结果。你可以检查所返回的ActionResult对象,以确保你接收了预期的结果。
  • You don't need to simulate client requests. The MVC Framework model binding system allows you to write action methods that receive input as method parameters. To test an action method, you simply call the action method directly and provide the parameter values that interest you. 
    你不需要模拟客户端请求。MVC框架的模型绑定系统允许你编写以方法参数接收输入的动作方法。要测试一个动作方法,你只要简单地直接调用该动作方法,并提供你感兴趣的参数值。

We will show you how to create unit tests for the different kinds of action results as we go through this chapter. 
随着本书的进行,我们将向你演示如何生成各种动作结果的单元测试。

Don't forget that unit testing isn't the complete story. Complex behaviors in an application arise when action methods are called in sequence. Unit testing is best combined with other testing approaches. 
不要忘记,单元测试并不是事情的全部。当动作方法被依次调用时,会出现应用程序的复杂行为。单元测试最好与其它测试办法相结合。

We can use the RedirectResult class by creating a new instance and returning it from our action method. Listing 12-10 shows our DerivedController class updated to have two action methods, one of which uses RedirectResult to redirect requests to the other. 
通过在动作方法中生成RedirectResult的新实例并将其返回,我们可以使用这个RedirectResult类。清单12-10演示了我们的DerivedController类,它被更新成有两个动作方法,其中一个用RedirectResult把请求重定向到另一个。

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"); } } } 

If you start the application and navigate to /Derived/Redirect, your browser will be redirected to /Derived/Index. To make our code simpler, the Controller class includes convenience methods for generating different kinds of ActionResults. So, for example, we can achieve the effect in Listing 12-10 by returning the result of the Redirect method, as shown in Listing 12-11. 
如果你启动这个应用程序,并导航到/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"); } 

There is nothing in the action result system that is very complex, and you end up with simpler, cleaner and more consistent code. Also, you can unit test your action methods very easily. In the case of a redirection, for example, you can simply check that the action method returns an instance of RedirectResult and that the Url property contains the target you expect. 
在复杂的动作结果系统中再没有其它东西了,你最终实现了更简单、更清晰、且更具一致性的代码。而且,你可以很容易地测试你的动作方法。例如,在这个重定向案例中,你可以简单地检查该动作方法返回了一个RedirectResult实例,以及Url属性含有你预期的目标。

The MVC Framework contains a number of built-in action result types, which are shown in Table 12-2. All of these types are derived from ActionResult, and many of them have convenient helper methods in the Controller class. 
MVC框架含有许多内建的动作结果类型,如表12-2所示。所有这些类型都派生于ActionResult,其中有不少在Controller类中有便利的辅助方法。

Table 12-2. Built-in ActionResult Types
表2-2. 内建的ActionResult类型
Type
类型
Description
描述
Controller Helper Methods
控制器辅助方法
ViewResultRenders the specified or default view template
返回指定的或默认的视图模板
View
PartialViewResultRenders the specified or default partial view template
返回指定的或默认的局部视图模板
PartialView
RedirectToRouteResultIssues an HTTP 301 or 302 redirection to an action method or specific route entry, generating a URL according to your routing configuration
将HTTP 301或302重定向发送给一个动作方法或特定的路由条目,根据你的路由配置生成一个URL
RedirectToAction
RedirectToActionPermanent
RedirectToRoute
RedirectToRoutePermanent
RedirectResultIssues an HTTP 301 or 302 redirection to a specific URL
将HTTP 301或302重定向发送给一个特定的URL
Redirect
RedirectPermanent
ContentResultReturns raw textual data to the browser, optionally setting a content-type header
把原始的文字数据返回给浏览器,可选地设置一个content-type(内容类型)头
Content
FileResultTransmits binary data (such as a file from disk or a byte array in memory) directly to the browser
直接把二进制数据(如磁盘文件内存中的字节数组)传输给浏览器
File
JsonResultSerializes a .NET object in JSON format and sends it as the response
序列化一个JSON格式的.NET对象,并把它作为响应进行发送
Json
JavaScriptResultSends a snippet of JavaScript source code that should be executed by the browser (this is intended for use only in AJAX scenarios, described in Chapter 19) 
发送一个应当由浏览器执行的JavaScript源代码片段(这只是打算提供给AJAX场合使用的,在第19章中描述)
JavaScript
HttpUnauthorizedResultSets the response HTTP status code to 401 (meaning “not authorized”), which causes the active authentication mechanism (forms authentication or Windows authentication) to ask the visitor to log in
将响应的HTTP状态码设置为401(意为“未授权”),这会引发当前的认证机制(表单认证或Windows认证)来要求访问者登录
None
HttpNotFoundResultReturns a HTTP 404 – Not found error
返回一个HTTP 404 — 未找到错误
HttpNotFound
HttpStatusCodeResultReturns a specified HTTP code
返回一个指定的HTTP码
None
EmptyResultDoes nothing
什么也不做
None

In the following sections, we'll show you how to use these results, as well how to create and use custom action results. 
在以下小节中,我们将向你演示如何使用这些结果,以及如何生成和使用自定义动作结果。

Returning HTML by Rendering a View
通过渲染视图返回HTML

The most common kind of response from an action method is to generate HTML and send it to the browser. When using the action result system, you do this by creating an instance of the ViewResult class that specifies the view you want rendered in order to generate the HTML, as demonstrated in Listing 12-12. 
一个动作方法最常用的一种响应形式是生成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"); } } } 

In this listing, we use the View helper method to create an instance of the ViewResult class, which is then returned as the result of the action method. 
在这个清单中,我们使用View辅助方法来生成ViewResult类的一个实例,然后它作为该动作方法的结果被返回。

■ Note Notice that that the return type is ViewResult. The method would compile and work just as well if we had specified the more general ActionResult type. In fact, some MVC programmers will define the result of every action method as ActionResult, even when they know it will always return a more specific type. We prefer to return the most specific type we know the method will return, which is a general object-oriented convention. We have been particularly diligent in this practice in the examples that follow to make it clear how you can use each result type. 
注:注意,返回类型是ViewResult。该方法被编译并运行,就好像我们已经指定了更一般的ActionResult类型一样。事实上,有些MVC程序员会把每个动作方法的结果都定义成ActionResult,即使他们知道该动作方法返回的是一个更特定的类型。在知道方法返回的类型时,我们更喜欢用最具体的类型,这是常规的面向对象约定。在以下的例子中,我们特别注重了这种实践,以使你清楚可以如何去使用每一种结果类型。

You specify the view you want rendered using the parameter to the View method. In this example, we have specified the Homepage view. 
通过送给View方法的参数,你指定了你要渲染的视图。在这个例子中,我们已经指定了Homepage视图。

■ Note We could have created the ViewResult object explicitly, (with return new ViewResult { ViewName = "Homepage" };). This is a perfectly acceptable approach, but we prefer to use the convenient helper methods defined by the Controller class. 
注:我们可能明确地生成了ViewResult对象,(用return new ViewResult { ViewName = "Homepage" };)。这是一个完全可接受的办法,但我们更喜欢使用由Controller类所定义的便利辅助方法。

When the MVC Framework calls the ExecuteResult method of the ViewResult object, a search will begin for the view that you have specified. If you are using areas in your project, then the framework will look in the following locations: 
当MVC框架调用ViewResult对象的ExecuteResult方法时,将开始搜索你已经指定的视图。如果在你的项目中使用了区域,那么框架将查找以下位置:

  • /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

You can see from the list that the framework looks for views that have been created for the legacy ASPX view engine (the .aspx and .ascx file extensions), even though we specified Razor when we created the project. The framework also looks for C# and Visual Basic .NET Razor templates (the .cshtml files are the C# ones and .vbhtml are Visual Basic; the Razor syntax is the same in these files, but the code fragments are, as the names suggest, in different languages). The MVC Framework checks to see if each of these files exists in turn. As soon as it locates a match, it uses that view to render the result of the action method. 
你可以从上述列表看出,框架也查找了遗留视图引擎生成的视图(.aspx和.ascs文件扩展名),即使我们在生成该项目时指定的是Razor。框架也查找了C#和VB的.NET Razor模板(.cshtml文件为C#模板的,.vbhtml是VB的)。MVC框架依次检查这些文件是否存在。只要它定位到一个匹配,它就用这个视图来渲染该动作方法的结果。

If you are not using areas, or you are using areas but none of the files in the preceding list have been found, then the framework continues its search, using the following locations: 
如果你未使用区域,或者你使用了区域,但在前述列表中没找到文件,那么框架会使用以下的位置继续它的搜索:

  • /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

Once again, as soon as the MVC Framework tests a location and finds a file, then the search stops, and the view that has been found is used to render the response to the client. 
一次地,只要MVC框架检查一个位置并找到一个文件,搜索便停止,而已经找到的这个视图便被用来把响应渲染给客户端。

In Listing 12-12, we are not using areas, so the first place that the framework will look will be /Views/Example/Index.aspx. Notice that the Controller part of the class name is omitted, so that creating a ViewResult in ExampleController leads to a search for a directory called Example. 
在清单12-12中,我们未使用区域,因此框架查找的第一个位置将是/Views/Example/Index.aspx。注意,这个类名的Controller部分是被忽略的(意即,ExampleController的Controller被忽略 — 译者注),因此,在ExampleController中生成的ViewResult会导致对名为Example的目录的搜索。

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. 
为了测试一个动作方法渲染的视图,你可以检查它所返回的ViewResult对象。这并不是十分相同的事情 — 必竟,你并不是在通过查检最终生成的HTML来跟踪这一过程 — 但也十分密切,只要你有理由确信MVC框架视图系统能恰当地工作。

The first situation to test is when an action method selects a specific view, like this: 
要测试的第一个情况是当动作方法选择了一个特定视图时,像这样:

public ViewResult Index() { return View("Homepage"); } 

You can determine which view has been selected by reading the ViewName property of the ViewResult object, as shown in this test method: 
通过读取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); } 

A slight variation arises when you are testing an action method that selects the default view, like this one: 
当你测试选择了默认视图的动作方法时,稍有不同,像这样:

public ViewResult Index() { return View(); } 

In such situations, you need to accept the empty string ("") for the view name, like this: 
在这种情况下,你需要对视图名用空字符串(指在测试方法中 — 译者注),像这样:

Assert.AreEqual("", result.ViewName); 

The sequence of directories that the MVC Framework searches for a view is another example of convention over configuration. You don't need to register your view files with the framework. You just put them in one of a set of known locations, and the framework will find them. 
MVC框架搜索视图目录的顺序是“约定优于配置”这一规范的另一个例子。你不需要用框架来注册你的视图文件。只需要把它们放在一组已知位置的一个位置上,框架会找到它们。

■ Tip The naming convention for locating views is customizable. We show you how to do this in Chapter 15. 
提示:定位视图的命名约定是可定制的。我们将在第15章向你演示如何去做。

We can take the convention a step further by omitting the name of the view we want rendered when we call the View method, as shown in Listing 12-13. 
在我们调用View方法时,通过忽略待渲染视图名,我们可以使这个约定更进一步,如清单12-13所示。

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(); } } } 

When we do this, the MVC Framework assumes that we want to render a view that has the same name as the action method. This means that the call to the View method in Listing 12-13 starts a search for a view called Index. 
当我们这样做时,MVC框架假设我们想渲染的是一个与动作方法同名的视图。意即,清单12-13中对View方法的调用将启动对一个名字为Index的视图的搜索。

■ Note The effect is that a view that has the same name as the action method is sought, but the name of the view is actually determined from the RouteData.Values["action"] value, which you saw in Listing 12-7 and was explained as part of the routing system in Chapter 11. 
注:其效果是搜寻一个与动作方法同名的视图,但视图名实际上是根据RouteData.Values[“action”]的值所确定的,这就是你在清单12-7中以及在第11章路由系统部分所看到的情况。

There are a number of overridden versions of the View method. They correspond to setting different properties on the ViewResult object that is created. For example, you can override the layout used by a view by explicitly naming an alternative, like this: 
View方法有许多重载版本。它们对应于在所生成的ViewResult对象上设置不同的属性。例如,通过明确地命名一个布局,你可以覆盖一个视图所使用的(默认)布局,像这样:

public ViewResult Index() { return View("Index", "_AlternateLayoutPage"); } 

译者注:在这个方法中,"_AlternateLayoutPage"参数给出的是布局文件名(不带扩展名),该布局应当是/View/Shared/文件夹中的_AlternateLayoutPage.cshtml文件。

SPECIFYING A VIEW BY ITS PATH
通过路径指定视图

The naming convention approach is convenient and simple, but it does limit the views you can render. If you want to render a specific view, you can do so by providing an explicit path and bypass the search phase. Here is an example: 
命名约定的办法方便而简单,但它限制了你可以渲染的视图。如果你想渲染一个特定的视图,你可以通过提供一个明确的路径并绕过搜索阶段来做这件事。以下是一个例子:

using System.Web.Mvc; namespace ControllersAndActions.Controllers { public class ExampleController : Controller { public ViewResult Index() { return View("~/Views/Other/Index.cshtml"); } } } 

When you specify a view like this, the path must begin with / or ~/ and include the file name extension (such as .cshtml for Razor views containing C# code). 
当你像这样指定了一个视图时,路径必须从/或~/开始,并包括文件扩展名(例如,.cshtml对应的是含有C#代码的Razor视图)。

If you find yourself using this feature, we suggest that you take a moment and ask yourself what you are trying to achieve. If you are attempting to render a view that belongs to another controller, then you might be better off redirecting the user to an action method in that controller (see the “Redirecting to an Action Method” section later in this chapter for an example). If you are trying to work around the naming scheme because it doesn't suit the way you have organized your project, then see Chapter 15, which explains how to implement a custom search sequence. 
如果你感到你要用这一特性,我们建议你花一些时间,并自问一下你要达到什么结果。如果是试图渲染属于另一个控制器的视图,那么你也许最好把用户重向到那个控制器的一个动作方法(参见本章稍后“重定向到一个动作方法”小节的例子)。如果是试图围绕命名方案进行工作,因为它不适合你项目的组织方式,那么参见第15章,它解释了如何实现一个自定义搜索顺序。(这一小节的含义是你最好不要用这种办法直接指定视图,这不利于应用程序的进一步扩展和维护 — 译者注)

Passing Data from an Action Method to a View
把数据从动作方法传递给视图

We often need to pass data from an action method to a view. The MVC Framework provides a number of different ways of doing this, which we describe in the following sections. In these sections, we touch on the topic of views, which we cover in depth in Chapter 15. In this chapter, we’ll discuss only enough view functionality to demonstrate the controller features of interest. 
我们通常需要把数据从一个动作方法传递给一个视图。MVC框架提供了许多做这种事情的不同方式,我们将在以下小节中加以描述。在这些小节中,我们略微谈到了视图的论题,在第15章作深度讨论。在本章中,我们将只讨论足以演示我们感兴趣的控制器特性所需要的视图功能性。

Providing a View Model Object
提供视图模型对象

You can send an object to the view by passing it as a parameter to the View method, as shown in Listing 12-14. 
你可以把一个对象作为View方法的参数,把这个对象发送给视图,如清单12-14所示。

Listing 12-14. Specifying a View Model Object

public ViewResult Index() { DateTime date = DateTime.Now; return View(date); } 

We have passed a DateTime object as the view model. We can access the object in the view using the Razor Model keyword, as shown in Listing 12-15. 
我们已经传递了一个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) 

The view shown in Listing 12-15 is known as an untyped or weakly typed view. The view doesn't know anything about the view model object, and treats it as an instance of object. To get the value of the DayOfWeek property, we need to cast the object to an instance of DateTime. This works, but produces messy views. We can tidy this up by creating strongly typed views, in which we tell the view what the type of the view model object will be, as demonstrated in Listing 12-16. 
清单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 

We specify the view model type using the Razor model keyword. Notice that we use a lowercase m when we specify the model type and an uppercase M when we read the value. Not only does this help tidy up our view, but Visual Studio supports IntelliSense for strongly typed views, as shown in Figure 12-3. 
我们用Razor的model关键词指定了视图的模型类型。注意,当我们指定模型类型时,使用小写的m,而在读取值时使用大写的M。这样做不仅便于整洁我们的视图,而且Visual Studio对强类型视图支持智能感应,如图12-3所示。

图12-3

Figure 12-3. IntelliSense support for strongly typed views
图12-3. 支持强类型视图的智能感应

UNIT TEST: VIEW MODEL OBJECTS
单元测试:视图模型对象

You can access the view model object passed from the action method to the view through the ViewResult.ViewData.Model property. Here is a simple action method: 
你可以通过ViewResult.ViewData.Model属性来访问从动作方法传递给视图的视图模型对象。以下是一个简单的动作方法:

public ViewResult Index() { return View((object)"Hello, World"); } 

This action method passes a string as the view model object. We have cast this to object so that the compiler doesn't think that we want the overload of View that specifies a view name. We can access the view model object through the ViewData.Model property, as shown in this test method: 
该动作方法传递一个字符串作为视图模型对象。我们已经把它转换成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); } 

Passing Data with the ViewBag
用ViewBag传递数据

We introduced the View Bag feature in Chapter 3. This feature allows you to define arbitrary properties on a dynamic object and access them in a view. The dynamic object is accessed through the Controller.ViewBag property, as demonstrated in Listing 12-17. 
在第3章中我们介绍过View Bag(视图包)特性。该特性允许你在一个动态对象上定义任意属性,并在视图中访问它们。这个动态对象可以通过Controller.ViewBag属性进行访问,如清单12-17所示。

Listing 12-17. Using the View Bag Feature

public ViewResult Index() { ViewBag.Message = "Hello"; ViewBag.Date = DateTime.Now; return View(); } 

In the listing, we have defined properties called Message and Date simply by assigning values to them. Prior to this point, no such properties existed, and we made no preparations to create them. To read the data back in the view, we simply get the same properties that we set in the action method, as Listing 12-18 shows. 
在该清单中,我们通过简单赋值的办法,已经定义了名为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 

The ViewBag has an advantage over using a view model object in that it is easy to send multiple objects to the view. If we were restricted to using view models, then we would need to create a new type that had string and DateTime members in order to get the same effects as Listings 12-17 and 12-18. 
ViewBag在使用一个视图模型对象方面长处是它便于把多个对象发送给视图。如果我们只能使用视图模型,那么,为了获得清单12-17和12-18的同样效果,我们就需要生成一个新类型,它具有string和DateTime成员。

When working with dynamic objects, you can enter any sequence of method and property calls in the view, like this: 
当用动态对象进行工作时,你可以在视图中调用方法和属性的任意序列,像这样:

The day is: @ViewBag.Date.DayOfWeek.Blah.Blah.Blah 

Visual Studio can't provide IntelliSense support for any dynamic objects, including the ViewBag, and errors such as this won't be revealed until the view is rendered. 
Visual Studio不能提供对任何动态对象的智能感应支持,包括ViewBag,而且在视图被渲染之前不支持诸如“对此无法展示”之类的错误提示

■ Tip We like the flexibility of the ViewBag, but we tend to stick to strongly typed views. There is nothing to stop us from using both view models and the View Bag feature in the same view. Both will work alongside each other without interference. 
提示:我们喜欢ViewBag的灵活性,但我们也喜欢使用强类型视图。在同一个视图中既使用视图模型也使用View Bag是没有限制的。它们两者可以无干扰地一起工作。

UNIT TEST: VIEWBAG
单元测试ViewBag

You can read values from the ViewBag through the ViewResult.ViewBag property. The following test method is for the action method in Listing 12-17: 
你可以通过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); } 

Passing Data with View Data
用View Data传递数据

The View Bag feature was introduced with MVC version 3. Prior to this, the main alternative to using view model objects was view data. The View Data feature similarly to the View Bag feature, but it is implemented using the ViewDataDictionary class rather than a dynamic object. The ViewDataDictionary class is like a regular key/value collection and is accessed through the ViewData property of the Controller class, as shown in Listing 12-19. 
View Bag特性是随MVC的第3版引入的。在此之前,使用视图模型对象的主要办法是视图数据。View Data(视图数据)特性类似于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(); } 

You read the values back in the view as you would for any key/value collection, as shown in Listing 12-20. 
就像使用“键/值”集合那样,你可以在视图中读回这些数据值,如清单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"] 

You can see that we must cast the object that we get from the view data, much as we did for the untyped view. 
你可以看出,我们必须对从视图数据获得的对象进行转换,这十分类似于我们对非类型视图所做的那样。

■ Note We are less fond of the View Data feature now that the View Bag feature is available, but we still favor strongly typed views and view models wherever possible. 
注:在View Bag特性可用的情况下,我们现在不太喜欢View Data特性了,但在可能的情况下,我们仍然喜欢强类型视图和视图模型。

UNIT TEST: VIEW DATA
单元测试:View Data

You can test action methods that use the View Data feature by reading values from ViewResult.ViewData. The following test is for the action method in Listing 12-19: 
通过读取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"]); } 

Performing Redirections
执行重定向

A common result from an action method is not to produce any output directly, but to redirect the user's browser to another URL. Most of the time, this URL is another action method in the application that generates the output you want the users to see. 
一个动作方法的通常结果并不是直接产生输出,而是把用户的浏览器重定向到另一个URL。大多数情况下,这个URL是应用程序中的另一个动作方法,它生成你希望用户看到的输出。

THE POST/REDIRECT/GET PATTERN
POST/Redirect/GET模式

The most frequent use of a redirect is in action methods that process HTTP POST requests. As we mentioned in the previous chapter, POST requests are used when you want to change the state of an application. If you just return HTML following the processing of a request, you run the risk that the user will click the browser's reload button and resubmit the form a second time, causing unexpected and undesirable results. 
重定向最经常的应用是用在处理HTTP POST请求的动作方法中。正如我们在上一章所提到的,当你希望修改应用程序状态时,才会使用POST请求。如果你只是简单地在请求处理之后马上返回HTML,你就陷入这样的风险:用户点击浏览器的重载按钮(意指“刷新当前页面” — 译者注),并再次递交该表单、引发异常及不符需求的结果。

To avoid this problem, you can follow the pattern called Post/Redirect/Get. In this pattern, you receive a POST request, process it, and then redirect the browser so that a GET request is made by the browser for another URL. GET requests should not modify the state of your application, so any inadvertent resubmissions of this request won't cause any problems. 
为了避免这种问题,你可以遵循叫做Post/Redirect/Get的模式。在这个模式中,你接收一个POST请求、处理它、然后重定向浏览器,以便由浏览器形成另一个GET请求的URL。GET请求不应该修改你应用程序的状态,因此,该请求的任何不经意的再次递交都不会引起任何问题。

When you perform a redirect, you send one of two HTTP codes to the browser: 
在执行重定向时,给浏览器发送的是以下两个HTTP代码之一:

  • Send the HTTP code 302, which is a temporary redirection. This is the most frequently used type of redirection and, until MVC 3, was the only kind for which the MVC Framework had built-in support. When using the Post/Redirect/Get pattern, this is the code that you want to send. 
    发送HTTP代码302,这是一个临时重定向。这是最常用的重定向类型,而且直到MVC 3,这是MVC框架内建支持的唯一的一种重定向。当使用Post/Redirect/Get模式时,这是你要发送的代码。
  • Send the HTTP code 301, which indicates a permanent redirection. This should be used with caution, because it instructs the recipient of the HTTP code not to request the original URL ever again and to use the new URL that is included alongside the redirection code. If you are in doubt, use temporary redirections; that is, send code 302. 
    发送HTTP代码301,它表示一个永久重定向。应该小心地使用它,因为它指示HTTP代码接收器不要再次请求原始URL,并使用包含重定向代码所伴随的新URL。如果你有疑问,请使用临时重定向,即,发送代码302。

Redirecting to a Literal URL
重定向到字面URL

The most basic way to redirect a browser is to call the Redirect method, which returns an instance of the RedirectResult class, as shown in Listing 12-21. 
重定向浏览器最基本的方式是调用Redirect方法,它返回RedirectResult类的一个实例,如清单12-21所示。

Listing 12-21. Redirecting to a Literal URL

public RedirectResult Redirect() { return Redirect("/Example/Index"); } 

The URL you want to redirect to is expressed as a string and passed as a parameter to the Redirect method. The Redirect method sends a temporary redirection. You can send a permanent redirection using the RedirectPermanent method, as shown in Listing 12-22. 
你要重定向的URL应当表示成一个字符串,并作为传递给Redirect方法的参数。Redirect方法发送一个临时重定向。你可以用RedirectPermanent方法发送一个永久重定向,如清单12-22所示。

Listing 12-22. Permanently Redirecting to a Literal URL

public RedirectResult Redirect() { return RedirectPermanent("/Example/Index"); } 

■ Tip If you prefer, you can use the overloaded version of the Redirect method, which takes a bool parameter that specifies whether or not a redirection is permanent. 
提示:如果你喜欢,你可以用Redirect方法的重载版本,它以一个布尔型参数指定是否永久重定向。

UNIT TEST: LITERAL REDIRECTIONS
单元测试:字面重定向

Literal redirections are easy to test. You can read the URL and whether the redirection is permanent or temporary using the Url and Permanent properties of the RedirectResult class. The following is a test method for the redirection shown in Listing 12-21. 
字面重定向易于测试。你可以用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); } 

Redirecting to a Routing System URL
重定向到路由系统的一个URL

If you are redirecting the user to a different part of your application, you need to make sure that the URL you send is valid within your URL schema, as described in the previous chapter. The problem with using literal URLs for redirection is that any change in your routing schema means that you need to go through your code and update the URLs. 
如果你要把用户重定向到应用程序的一个不同的部分,你需要确保你发送的URL符合之前章节所描述的URL模式。用字面URL进行重定向的问题是,对路由方案的任何修改,都意味着你需要检查你的代码,并对URL进行更新。

As an alternative, you can use the routing system to generate valid URLs with the RedirectToRoute method, which creates an instance of the RedirectToRouteResult, as shown in Listing 12-23. 
一种可选的替代办法是,你可以运用路由系统,以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" }); } 

The RedirectToRoute method issues a temporary redirection. Use the RedirectToRoutePermanent method for permanent redirections. Both methods take an anonymous type whose properties are then passed to the routing system to generate a URL. For more details of this process, see the “Generating Outgoing URLs” section of Chapter 11. 
RedirectToRoute方法发布一个临时重定向。对于永久重定向,使用RedirectToRoutePermanent方法。这两个方法都以一个匿名类型为参数,其属性然后被传递给路由系统,以生成一个URL。此过程的更多细节请参阅第11章的“生成输出URL”小节。

UNIT TESTING: ROUTED REDIRECTIONS
单元测试:路由的重定向

Here is an example that tests the action method in Listing 12-23: 以下是测试清单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"]); } 

Redirecting to an Action Method
重定向到一个动作方法

You can redirect to an action method more elegantly by using the RedirectToAction method. This is just a wrapper around the RedirectToRoute method that lets you specify values for the action method and the controller without needing to create an anonymous type, as shown in Listing 12-24. 
通过使用RedirectToAction方法,你可以更雅致地重定向到一个动作方法。这只是RedirectToAction方法一个封装程序,让你指定动作方法和控制器的值,而不需要生成一个匿名类型,如清单12-24所示。

Listing 12-24. Redirecting Using the RedirectToAction Method

public RedirectToRouteResult Redirect() { return RedirectToAction("Index"); } 

If you just specify an action method, then it is assumed that you are referring to an action method in the current controller. If you want to redirect to another controller, you need to provide the name as a parameter, like this: 
如果你只指定一个动作方法,那么它假设你指向的是当前控制器的一个动作方法。如果你想重定向到另一个控制器,你需要以参数提供其名字,像这样:

return RedirectToAction("Index", "MyController"); 

There are other overloaded versions that you can use to provide additional values for the URL generation. These are expressed using an anonymous type, which does tend to undermine the purpose of the convenience method, but can still make your code easier to read. 
还有一些其它的重载版本,你可以用来为URL生成提供额外的值。这些是用一个匿名类型来表示的,这有点破坏了便利方法的目的,但仍然能使你的代码易于阅读。

■ Note The values that you provide for the action method and controller are not verified before they are passed to the routing system. You are responsible for making sure that the targets you specify actually exist. 
注:你为控制的动作方法所提供的值,在它们被传递给路由系统之前,是不会被检验的。你有责任要确保你所指定的目标是实际存在的。

The RedirectToAction method performs a temporary redirection. Use the RedirectToActionPermanent for permanent redirections. 
RedirectToAction方法执行一个临时重定向。用RedirectToActionPermanent进行永久重定向。

PRESERVING DATA ACROSS A REDIRECTION
跨越重定向保留数据

A redirection causes the browser to submit an entirely new HTTP request, which means that you don’t have access to the details of the original request. If you want to pass data from one request to the next, you can use the Temp Data feature. 
重定向引发浏览器递交一个全新的HTTP请求,这意味着你没有访问原始请求的细节。如果你希望把一个请求的数据传递给下一个请求,你可以用Temp Data特性。

TempData is similar to Session data, except that TempData values are marked for deletion when they are read, and they are removed when the request has been processed. This is an ideal arrangement for short-lived data that you want to persist across a redirection. Here is a simple example in an action method that uses the RedirectToAction method: 
TempData类似于Session数据,只不过,TempData的值在被读取之后,被标记为删除,并在该请求已经被处理之后被删除。这对于你跨越重定向希望而持续的短期数据是一种理想的安排。以下是使用了RedirectToAction方法的一个简单的动作方法:

public RedirectToRouteResult Redirect() { TempData["Message"] = "Hello"; TempData["Date"] = DateTime.Now; return RedirectToAction("Index"); } 

When this method processes a request, it sets values in the TempData collection, and then redirects the user’s browser to the Index action method in the same controller. You can read the TempData values back in the target action method, and then pass them to the view, like this: 
当这个方法处理一个请求时,它在TempData集合中设置了一些值,然后把用户的浏览器重定向到同一个控制器中的Index动作方法。你可以在目标动作方法中读回TempData的值,然后把它们传递给视图,像这样:

public ViewResult Index() { ViewBag.Message = TempData["Message"]; ViewBag.Date = TempData["Date"]; return View(); } 

A more direct approach would be to read these values in the view, like this: 
更直接的办法是在视图中读取这些值,像这样:

@{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @(((DateTime)TempData["Date"]).DayOfWeek) <p /> The message is: @TempData["Message"] 

Reading the values in the view means that you don’t need to use the View Bag or View Data feature in the action method. However, you must cast the TempData results to an appropriate type. 
在视图中读取这些值,意味着你不需要在动作方法中使用View Bag或View Data特性。然而,你必须把TempData结果转换成相应的类型。

You can get a value from TempData without marking it for removal by using the Peek method, like this: 
利用Peek方法,你可以得到TempData的值,而不必把它标记为删除,像这样:

DateTime time = (DateTime)TempData.Peek("Date"); 

You can preserve a value that would otherwise be deleted by using the Keep method, like this: 
利用Keep方法,你可以保留一个否则将被删除的值,像这样:

TempData.Keep("Date"); 

The Keep method doesn’t protect a value forever. If the value is read again, it will be marked for removal once more. If you want to store items so that they won’t be removed automatically, use session data instead. 
Keep方法不会永久保护一个值。如果这个值被再次读取,它将被再次标记为删除。如果你想存储一些条目以使它们不会被自动删除,请使用会话数据。

Returning Text Data
返回文本数据

In addition to HTML, there are many other text-based data formats that you might want your web application to generate as a response. These include the following: 
除HTML之外,还有许多你希望web应用程序作为响应而生成的其它基于文本的数据格式。这些包括:

  • XML, as well as RSS and Atom (which are specific subsets of XML) 
    XML,以及RSS和Atom(特殊的XML子集)
  • JSON (usually for AJAX applications) 
    JSON(通常用于AJAX应用程序)
  • CSV (for exporting tabular data) 
    CSV(用于输出表数据)
  • Just plain text
    纯文本

The MVC Framework has specific support for JSON, which we will show you shortly. For all of the other data types, we can use the general-purpose ContentResult action result. Listing 12-25 provides a demonstration. 
MVC框架对JSON有特别支持,我们很快给你演示。对所有其它数据类型,我们可以使用多用途的ContentResult动作结果。清单12-25提供了一个演示。

Listing 12-25. Returning Text Data from an Action Method

public ContentResult Index() { string message = "This is plain text"; return Content(message, "text/plain", Encoding.Default); } 

We create ContentResult through the Controller.Content helper method, which takes three parameters: 
我们通过Controller.Content辅助方法生成了ContentResult,它有三个参数:

  • The first is the text data that you want to send. 
    第一个是你想要发送的文本数据。
  • The second is the value of the HTTP content-type header for the response. You can look up these easily online or use the System.Net.Mime.MediaTypeNames class to get a value. For plain text, the value is text/plain. 
    第二个是该响应的HTTP的content-type头的值。你可以很容易地在网上查寻这些值,或使用System.Net.Mime.MediaTypeNames类来得到一个值,对于纯文本,其值是text/palin。
  • The final parameter specifies the encoding scheme that will be used to convert the text into a sequence of bytes. 
    最后一个参数指定编码方案,它将被用来把该文本转换成字节序列。

You can omit the last two parameters, in which case the framework assumes that the data is HTML (which has the content type of text/html). It will try to select an encoding format that the browser has declared support for when it made the request you are processing. This allows you to return just text, like this: 
你可以忽略后两个参数,此时,框架假设该数据是HTML(其内容类型为text/html)。它将试图选择浏览器在形成这个请求时已经申明支持的编码格式。这允许你只返回文本,像这样:

return Content("This is plain text"); 

In fact, you can go a little further. If you return any object from an action method that isn't an ActionResult, the MVC Framework will try to serialize the data to a string value and send it to the browser as HTML, as shown in Listing 12-26. 
事实上,你可以往前走一小步。如果你从一个动作方法返回的对象不是一个ActionResult,MVC框架将试图把该数据序列化成一个字符串值,并把它作为HTML发送给浏览器,如清单12-26所示。

Listing 12-26. Returning a Non-ActionResult Object from an Action Method

public object Index() { return "This is plain text"; } 

You can see how the result is displayed in the browser in Figure 12-4. 
你可以从图12-4看到该结果在浏览器中是如何显示的。

图12-4

Figure 12-4. A browser displaying plain text delivered with a content-type value of text.html
图12-4. 显示以text/html的content-type值投递的纯文本的浏览器

UNIT TEST: CONTENT RESULTS
单元测试:内容结果

Testing action methods that return ContentResult is reasonably simple, as long as you are able to meaningfully compare the text in the test method. Here is a simple test for the action method shown in Listing 12-25: 
测试返回ContentResult的动作方法相当简单,只要你能够在该测试方法中有意义地比较文本即可。以下是针对清单12-25所示的动作方法的一个简单测试:

[TestMethod] public void ContentTest() { // Arrange - create the controller ExampleController target = new ExampleController(); // Act - call the action method ContentResult result = target.Index(); // Assert - check the result Assert.AreEqual("text/plain", result.ContentType); Assert.AreEqual("This is plain text", result.Content); } 

The ContentResult.Content property provides access to the content contained by the result, and the ContentType property can be used to get the MIME type for the data. The ContentResult class also defines a ContentEnconding property, but this is frequently omitted from unit tests because the value is often determined by the MVC Framework based on information provided by the user’s browser. 
ContentResult.Content属性提供了对结果所包含的内容的访问,而ContentType属性可以被用来获得数据的MIME类型。ContentResult类也定义了一个ContentEnconding属性,但这在单元测试中常常是被忽略的,因为这个值通常是由MVC模型基于用户浏览器所提供的信息来确定的。

Returning XML Data
返回XML数据

Returning XML data from an action method is very simple, especially when you are using LINQ to XML and the XDocument API to generate XML from objects. Listing 12-27 provides a demonstration. 
从一个动作方法返回XML数据是很简单的,特别是在你使用LINQ to XML和XDocument API通过对象生成XML时。清单12-27提供了一个演示。

Listing 12-27. Generating XML in an Action Method

public ContentResult XMLData() { StoryLink[] stories = GetAllStories(); XElement data = new XElement("StoryList", stories.Select(e => { return new XElement("Story", new XAttribute("title", e.Title), new XAttribute("description", e.Description), new XAttribute("link", e.Url)); })); return Content(data.ToString(), "text/xml"); } 

The StoryLink class that is used to generate the XML is defined like this: 
用来生成XML的StoryLink类是这样定义的:

public class StoryLink{ public string Title {get; set;} public string Description { get; set; } public string Url { get; set; } } 

The result of the action method is an XML fragment: 
该动作方法的结果是一个XML片段:

<StoryList> <Story title="First example story" description="This is the first example story" link="/Story/1" /> <Story title="Second example story" description="This is the second example story" link="/Story/2" /> <Story title="Third example story" description="This is the third example story" link="/Story/3" /> </StoryList> 

■ Tip If you are not familiar with LINQ to XML and the XDocument API, then it is worthy of investigation. They provide the simplest and most elegant way to work with XML that we have seen. Adam covers the topic in depth in the book he wrote with Joe Ratz, Pro LINQ in C# 2010, also published by Apress
提示:如果你不熟悉LINQ to XML以及XDocument API,作一些了解是值得的。它们提供了我们所见到过的与XML一起工作最简单且最雅致的方式。Adam在他与Joe Ratz合著的Pro LINQ in C# 2010一书中深度涉及了这一论题,该书也由Apress出版。

Returning JSON Data
返回JSON数据

In recent years, the use of XML documents and XML fragments in web applications has waned, in favor of the JavaScript Object Notation (JSON) format. JSON is a lightweight, text-based format that describes hierarchical data structures. JSON data is valid JavaScript code, which means that it is natively supported by all the mainstream web browsers, making it more compact and easier to work with than XML. JSON is most commonly used to send data from the server to a client in response to AJAX queries, which we’ll cover in depth in Chapter 19. 
最近几年,在web应用程序中运用XML文档以及XML片段已经衰退,而有利于使用JavaScript Object Notation(脚本对象注释 — JSON)格式。JSON是描述层次数据结构的一种轻量级的、基于文本的格式。JSON数据是合法的JavaScript代码,意即它得到了所有主流web浏览器的支持,使它比XML更紧凑且更易于使用。JSON最常用于把数据从服务器发送到客户端以适应我们将在第19章深度涉及的AJAX查询。

The MVC Framework has a built-in JsonResult class, which takes care of serializing .NET objects into the JSON format. You can create JsonResult objects using the Controller.Json convenience method, as shown in Listing 12-28. 
MVC框架有内建的JsonResult类,它负责把.NET对象序列化成JSON格式。你可以用Controller.Json便利方法生成JsonResult对象,如清单12-28所示。

Listing 12-28. Creating JSON Data with the JsonResult Class

[HttpPost] public JsonResult JsonData() { StoryLink[] stories = GetAllStories(); return Json(stories); } 

This example uses the same StoryLink class you saw previously, but we don’t need to manipulate the data, since the serialization is taken care of by the JsonResult class. The response generated by the action method in Listing 12-28 is as follows: 
本例使用你之前看到的同一个StoryLink类,但我们不需要操纵这些数据,因为序列化是由JsonResult类负责的。清单12-28的动作方法所生成的响应如下:

[{"Title":"First example story", "Description":"This is the first example story","Url":"/Story/1"}, {"Title":"Second example story", "Description":"This is the second example story","Url":"/Story/2"}, {"Title":"Third example story", "Description":"This is the third example story","Url":"/Story/3"}] 

We have formatted the JSON data to make it more readable. Don’t worry if you are not familiar with JSON. We will return to this topic in Chapter 19 and provide some detailed examples. Also, for more details about JSON, visit http://www.json.org. 
我们已经格式化了JSON数据以使它更可读。如果你不熟悉JSON不必着急。我们将在第19章回到这一论题,并提供一些详细的示例。还有,更多JSON细节请访问http://www.json.org。

■ Note For security reasons, the JsonResult object will generate a response only for HTTP POST requests. This is to prevent data from being exposed to third parties via cross-site requests (we explain what these are in Chapter 21). We like to annotate JSON-generating action methods with HttpPost as a reminder of this behavior, although that is not essential. We explain how to disable this behavior in Chapter 19. 
注:出于安全性原因,JsonResult对象将只生成HTTP POST请求的响应。目的是防止通过跨网站请求把数据暴露给第三方(我们将在第21章进行解释)。我们喜欢用HttpPost注释生成JSON的动作方法,以作为这种行为的提醒,尽管这不是实质性的。我们将在第19章解释如何取消这种行为。

Returning Files and Binary Data
返回文件及二进制数据

FileResult is the abstract base class for all action results concerned with sending binary data to the browser. The MVC Framework comes with three built-in concrete subclasses for you to use: 
FileResult是与所有把二进制数据发送给浏览器的动作结果相关的抽象基类。MVC框架提供了三个内建的具体子类供你使用:

  • FilePathResult sends a file directly from the server file system. 
    FilePathResult直接从服务器的文件系统发送一个文件。
  • FileContentResult sends the contents of an in-memory byte array. 
    FileContentResult发送内存字节数组的内容。
  • FileStreamResult sends the contents of a System.IO.Stream object that is already open. 
    FileStreamResult发送已经被打开的System.IO.Stream对象的内容。

You don’t need to worry about which of these types to use, because they are created for you automatically by the different overloads of the Controller.File helper method. You will see demonstrations of these overloads in the following sections. 
你不需要担心要使用哪个类型,因为它们是通过Controller.File辅助方法的各个重载为你自动生成的。你将在以下小节中看到这些重载的演示。

Sending a File
发送一个文件

Listing 12-29 demonstrates how to send a file from the disk. 
清单12-29演示如何发送一个磁盘文件。

Listing 12-29. Sending a File

public FileResult AnnualReport() { string filename = @"c:\AnnualReport.pdf"; string contentType = "application/pdf"; string downloadName = "AnnualReport2011.pdf"; return File(filename, contentType, downloadName); } 

This action method causes the browser to prompt the user to save the file, as shown in Figure 12-5. Different browsers handle file downloads in their own ways. The figure shows the prompt that Internet Explorer 8 presents. 
该动作方法引发浏览器提示用户保存该文件,如图12-5所示。不同的浏览器以它们自己的方式处理文件下载。该图显示了Internet Explorer 8所表现的提示。

图12-5

Figure 12-5. Browser open or save prompt
图12-5. 浏览器的“打开或保存”提示

There are three parameters to the overload of the File method we using in the listing, which are described in Table 12-3. 
我们在上例中所用的File方法有三个参数的重载,其描述如表12-3。

Table 12-3. Parameters Passed to the File Method when Transmitting a File
Parameter
参数
Required? 
必需的?
Type
类型
Description
描述
filenameYesstringThe path of the file (in the server’s file system) to be sent. 
被发送的文件路径(在服务器的文件系统中)
contentTypeYesstringThe MIME type to use as the response’s content-type header. The browser will use this MIME type information to decide how to deal with the file. For example, if you specify application/vnd.ms-excel, the browser may offer to open the file in Microsoft Excel. Similarly, application/pdf responses should be opened in the user’s chosen PDF viewer. 
用作为响应的content-type头的MIME类型。浏览器将用这个MIME类型信息来决定如何处理该文件。例如,如果你指定application/vnd.ms-excel,浏览器将意图打开Microsoft Excel文件。类似地,application/pdf响应应该在用户的PDF查看程序中打开。
fileDownloadNameNostringThe content-disposition header value to send with the response. When this parameter is specified, the browser should always pop up a save-or-open prompt for the downloaded file. The browser should treat this value as the file name of the downloaded file, regardless of the URL from which the file is being downloaded. 
随响应发送的content-disposition头的值。当指定这个参数时,浏览器对下载文件应该总是弹出“保存或打开”提示。浏览器应该把这个值视为下载文件的文件名,而不管下载文件的URL是什么。

If you omit fileDownloadName and the browser knows how to display the MIME type itself (for example, all browsers know how to display an image/gif file), then the browser will display the file itself. 
如果你忽略fileDownloadName,而且浏览器自己知道如何显示MIME类型(例如,所有浏览器都知道如何显示image/gif文件),那么浏览器将自己显示该文件。

If you omit fileDownloadName and the browser doesn’t know how to display the MIME type (for example, you might specify application/vnd.ms-excel), then the browser will pop up a save-or-open prompt, guessing a suitable file name based on the current URL (and in Internet Explorer’s case, based on the MIME type you’ve specified). However, the guessed file name will almost certainly make no sense to the user, as it may have an unrelated file name extension such as .mvc, or no extension at all. So, always be sure to specify fileDownloadName when you expect a save-or-open prompt to appear. 
如果你忽略fileDownloadName,而浏览器不知道如何显示这个MIME类型(例如,你可能指定了application/vnd.ms-excel),那么,浏览器将弹出一个“保存或打开”提示,基于当前的URL猜测合适的文件名(在Internet Explorer中,基于你所指定的MIME类型)。然而,所猜测的文件名对用户几乎都是无意义的,因为它可能有一个不相关的.mvc文件扩展名,或根本没扩展名。因此,在你期望出现“保存或打开”提示的情况下,应当确保总是指定了fileDownloadName。

■ Caution The results are unpredictable if you specify a fileDownloadName that disagrees with the contentType parameter (for example, if you specify a file name of AnnualReport.pdf along with a MIME type of application/vnd.ms-excel). If you don’t know which MIME type corresponds to the file you’re sending, you can specify application/octet-stream instead. This means “some unspecified binary file.” It tells the browser to make its own decision about how to handle the file, usually based on the file name extension. 
注意:如果你指定了一个与contentType参数不匹配的fileDownloadName,结果是不可预测的(例如,如果你指定的文件名是AnnualReport.pdf,而MIME类型是application/vnd.ms-excel)。如果你不知道你要发送的文件对应的MIME类型,你可以指定application/octet-stream。其含义是“某种不明确的二进制文件”。它告诉浏览器自己去决定如何处理这个文件,这通常基于文件的扩展名。

Sending a Byte Array
发送字节数组

If you already have binary data in memory, you can transmit it to the browser using a different overload of the File method, as shown in Listing 12-30. 
如果你在内存中已经有二进制数据,你可以用不同的File重载方法把它传输给浏览器,如清单12-30所示。

Listing 12-30. Sending a Binary Array

public FileContentResult DownloadReport() { byte[] data = ... // Generate or fetch the file contents somehow return File(data, "application/pdf", "AnnualReport.pdf"); } 

We used this technique at the end of Chapter 9 when sending image data retrieved from the database. Once again, you must specify a contentType, and can optionally specify a fileDownloadName. The browser will treat these in exactly the same way as when you send a disk file. 
我们在第9章的末尾,当发送从数据库接收而来的数据时,使用了这一技术。再一次地,你必须指定contentType,并可以选择性地指定fileDownloadName。浏览器将像你发送一个磁盘文件一样地处理它们。

Sending the Contents of a Stream
发送流内容

If you are working with data that is available via an open System.IO.Stream, you can simply pass the stream to a version of the File method. The contents of the stream will be read and sent to the browser. This is demonstrated in Listing 12-31. 
如果你所处理的数据可以通过开放System.IO.Stream进行操作,你可以简单地把这个流传递给File方法的一个(重载)版本。这个流的内容将被读取并发送给浏览器。如清单12-31所示。

Listing 12-31. Sending the Contents of a Stream

public FileStreamResult DownloadReport(){ Stream stream = ...open some kind of stream... return File(stream, "text/html"); } 

UNIT TEST: FILE RESULTS
单元测试:文件结果

The base FileResult class defines two properties that are common to all three types of file results: the ContentType property returns the MIME type for the data, and the FileDownloadName property returns the name that the user will be prompted to save with. For more specific information, you must work with one of the concrete subclasses of FileResult. 
底层的FileResult类定义了两个属性,它们通用于所有三种类型的文件结果(指前述FilePathResult、FileContentResult、和FileStreamResult — 译者注):ContentType属性返回数据的MIME类型,FileDownloadName属性返回提示用户保存所用的文件名。对于更详细的信息,你必须用FileResult的上述三个具体子类之一进行工作。

When working with FilePathResult, you can get the name of the file that has been specified through the FileName property. The following test is for the action method shown in Listing 12-29: 
当用FilePathResult进行工作时,你可以得到已经通过FileName属性指定的文件名。以下测试是用于清单12-29所示的动作方法的:

[TestMethod] public void FileResultTest() { // Arrange - create the controller ExampleController target = new ExampleController(); // Act - call the action method FileResult result = target.AnnualReport(); // Assert - check the result Assert.AreEqual(@"c:\AnnualReport.pdf", ((FilePathResult)result).FileName); Assert.AreEqual("application/pdf", result.ContentType); Assert.AreEqual("AnnualReport2011.pdf", result.FileDownloadName); } 

The other concrete implementations have equivalent properties. When dealing with in-memory data, you can obtain the byte array through the FileContentResult.FileContents property. The FileStreamResult class defines a property called FileStream, which returns the System.IO.Stream that was passed to the result constructor. 
其它的具体实现有等同的属性。当处理内存数据时,你可以通过FileContentResult.FileContents属性获得字节数组。FileStreamResult类定义了一个名为FileStream的属性,它返回被传递给结果构造器的System.IO.Stream。

Returning Errors and HTTP Codes
返回错误及HTTP代码

The last of the built-in ActionResult classes that we will look at can be used to send specific error messages and HTTP result codes to the client. Most applications don’t require these features because the MVC Framework will automatically generate these kinds of results automatically. However, they can be useful if you need to take more direct control over the responses sent to the client. 
我们要考察的内建的ActionResult类的最后一个特性可以用来给客户端发送指定的错误消息和HTTP结果代码。大多数应用程序不需要这些特性,因为MVC框架将自动地生成这些种类的结果。然而,如果你需要对发送给客户端的响应有更直接的控制,它们可能是有用的。

Sending a Specific HTTP Result Code
发送一个特定的HTTP结果代码

You can send a specific HTTP status code to the browser using the HttpStatusCodeResult class. There is no controller helper method for this, so you must instantiate the class directly, as shown in Listing 12-32. 
利用HttpStatusCodeResult类,你可以把特定的HTTP状态码发送给浏览器。这个类没有对应的控制器辅助方法,因此,你必须直接对这个类进行实例化,如清单12-32所示

Listing 12-32. Sending a Specific Status Code

public HttpStatusCodeResult StatusCode() { return new HttpStatusCodeResult(404, "URL cannot be serviced"); } 

The constructor parameters for HttpStatusCodeResult are the numeric status code and an optional descriptive message. In the listing, we have returned code 404, which signifies that the requested resource doesn’t exist. 
HttpStatusCodeResult的构造器参数是数字状态码和一个可选的描述消息。在这个清单中,我们返回了代码404,它表示请求资源不存在。

Sending a 404 Result
发送404结果

We can achieve the same effect as Listing 12-32 using the more convenient HttpNotFoundResult class, which is derived from HttpStatusCodeResult and can be created using the controller HttpNotFound convenience method, as shown in Listing 12-33. 
利用更方便的HttpNotFoundResult类,我们可以获得清单12-32的同样效果,这个类派生于HttpStatusCodeResult,而且可以用控制器的HttpNotFound便利方法来生成,如清单12-33所示。

Listing 12-33. Generating a 404 Result

public HttpStatusCodeResult StatusCode() { return HttpNotFound(); } 

Sending a 401 Result
发送401结果

Another wrapper class for a specific HTTP status code is the HttpUnauthorizedResult, which returns the 401 code, used to indicate that a request is unauthorized. Listing 12-34 provides a demonstration. 
另一个特定HTTP状态码的封装程序类是HttpUnauthorizedResult,它返回401代码,用来指示一个未授权请求。清单12-34给出了一个演示。

Listing 12-34. Generating a 401 Result

public HttpStatusCodeResult StatusCode() { return new HttpUnauthorizedResult(); } 

There is no helper method in the Controller class to create instances of HttpUnauthorizedResult, so you must do so directly. The effect of returning an instance of this class is usually to redirect the user to the authentication page, as you saw in Chapter 9. 
在Controller类中没有辅助方法来生成HttpUnauthorizedResult的实例,因此你必须直接做。返回这个类实例的效果通常是把用户重定向到认证页面,如你在第9章所见到的那样。

UNIT TEST: HTTP STATUS CODES 单元测试:HTTP状态码

The HttpStatusCodeResult class follows the pattern you have seen for the other result types, and makes its state available through a set of properties. In this case, the StatusCode property returns the numeric HTTP status code, and the StatusDescription property returns the associated descriptive string. The following test method is for the action method in Listing 12-33: 
HttpStatusCodeResult类遵循了你在其它结果类型所看到的模式,它通过一组属性使它的状态可用。这里,StatusCode属性返回数值型HTTP状态码,而StatusDescription属性返回相关的描述字符串。以下测试方法是针对清单12-33动作方法的:

[TestMethod] public void StatusCodeResultTest() { // Arrange - create the controller ExampleController target = new ExampleController(); // Act - call the action method HttpStatusCodeResult result = target.StatusCode(); // Assert - check the result Assert.AreEqual(404, result.StatusCode); } 

Creating a Custom Action Result
生成自定义动作结果

The built-in action result classes are sufficient for most situations and applications, but you can create your own custom action results for those occasions when you need something special. In this section, we’ll demonstrate a custom action result that generates an RSS document from a set of objects. RSS is a format commonly used to publish frequently updated collections of items, such as headlines, to interested subscribers. Listing 12-35 shows our RssActionResult class. 
内建的动作结果类足以应付应用程序和大多数情况,但当你有某些特殊需要时,你可以生成自己的自定义动作结果。在本小节中,我们将演示一个自定义动作结果,它从一组对象生成一个RSS文档。RSS是一种常用于发布频繁更新的条目集的形式,如订户感兴趣的标题。清单12-35演示了我们的RssActionResult类。

Listing 12-35. Creating a Custom Action Result

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Xml.Linq; namespace ControllersAndActions.Infrastructure { public abstract class RssActionResult : ActionResult { } public class RssActionResult<T> : RssActionResult { public RssActionResult(string title, IEnumerable<T> data, Func<T, XElement> formatter) { Title = title; DataItems = data; Formatter = formatter; } public IEnumerable<T> DataItems { get; set; } public Func<T, XElement> Formatter { get; set; } public string Title { get; set; } public override void ExecuteResult(ControllerContext context) { HttpResponseBase response = context.HttpContext.Response; // set the content type of the response response.ContentType = "application/rss+xml"; // get the RSS content string rss = GenerateXML(response.ContentEncoding.WebName); // write the content to the client response.Write(rss); } private string GenerateXML(string encoding) { XDocument rss = new XDocument(new XDeclaration("1.0", encoding, "yes"), new XElement("rss", new XAttribute("version", "2.0"), new XElement("channel", new XElement("title", Title), DataItems.Select(e => Formatter(e))))); return rss.ToString(); } } } 

In fact, we have defined two classes. The first is an abstract class called RssActionResult, which subclasses ActionResult. The second is a strongly typed class named RssActionResult<T>, which is derived from RssActionResult. We have defined two classes so that we can create action methods that return the abstract RssActionResult class, but which create instances of the strongly typed subclass. 
事实上,我们定义了两个类。第一个是名为RssActionResult的抽象类,它是ActionResult的子类。第二个是名为RssActionResult<T>的强类型类,它派生于RssActionResult。我们已经定义了两个类,以使我们能够生成返回抽象RssActionResult类的动作方法,但生成的是强类型子类的实例。

The constructor of our custom action result, RssActionResult<T>, takes three parameters: the title for the RSS document we will generate, the set of data items that the document will contain, and a delegate that will be used to transform each data item into an XML fragment for inclusion in the RSS output. 
我们的自定义动作结果的构造器,RssActionResult<T>,有三个参数:我们要生成的RSS文档的标题、该文档包含的数据项集合、和一个委托,它将被用于把每个数据项传输到一个包括在RSS输出中的XML片段中。

■ Tip Notice that we have exposed the title, data items, and delegate as public properties. This is to enable easy unit testing, so that we can determine the status of the result without needing to call the ExecuteResult method. 
提示:注意,我们把标题、数据项、以及委派暴露为public属性。这样能够方便地单元测试,以使我们能够确定结果的状态,而不需要调用ExecuteResult方法。

To derive from the abstract ActionResult class, you must provide an implementation of the ExecuteResult method. Our example uses LINQ and the XDocument API to generate an RSS document. The document is written to the Response object, which is accessible through the ControllerContext parameter. Listing 12-36 shows an action method that uses our custom action result. 
为了从抽象的ActionResult类进行派生,你必须提供ExecuteResult方法的一个实现。我们的例子使用LINQ和XDocument API来生成一个RSS文档。该文档被写到Response对象,它可以通过ControllerContext参数进行访问。清单12-36演示了使用我们的自定义动作结果的动作方法。

Listing 12-36. Using a Custom Action Result

public RssActionResult RSS() { StoryLink[] stories = GetAllStories(); return new RssActionResult<StoryLink>("My Stories", stories, e => { return new XElement("item", new XAttribute("title", e.Title), new XAttribute("description", e.Description), new XAttribute("link", e.Url)); }); } 

To use our custom action result, we simply create a new instance of the strongly typed class and pass in the required parameters. In this example, we used the StoryLink class from previous examples, and the delegate that we defined (using a Func) generates the XML required for each story in the dataset. If you start the application and navigate to this action method, you generate an RSS document that is recognized by the browser, as shown in Figure 12-6. 
为了使用我们的自定义动作结果,我们简单地生成了一个新强类型实例,并在其中传递了所需要的参数。在这个例子中,我们使用了之前示例的StoryLink类,我们所定义的委派(用Func)生成数据集中每个故事的XML,如图12-6所示。

图12-6

Figure 12-6. The output from a custom action result
图12-6. 自定义动作结果的输出

posted on 2012-06-13 20:58  lucky.net  阅读(180)  评论(0编辑  收藏  举报

导航

Copyright luckynet 2013