15 视图 — 精通 MVC 3 框架

Views
视图

 

In Chapter 12, you saw how action methods can return ActionResult objects. As you learned, the moscommonly used action result is ViewResult, which causes a view to be rendered and returned to the client.
在第12章中,你看到了动作方法可以如何返回ActionResult对象。正如你所学到的那样,最常用的动作结果是ViewResult,它触发渲染一个视图并返回给客户端

You’ve seen views being used in many examples already, so you know roughly what they do. In this chapter, we’ll focus and clarify that knowledge. We’ll begin by showing you how the MVC Framework handles ViewResults using view engines, including demonstrating how to create a custom view engine. Next, we’ll describe techniques for working effectively with the built-in Razor View Engine. Then we’ll cover how to create and use partial views, child actions, and Razor sections. These are all essential topics for effective MVC development.
你已经在很多例子中看到了视图的运用,因此你大致上知道它们做什么。在本章中,我们将关注并澄清这方面的知识。我们将从向你演示MVC框架如何用视图引擎处理ViewResults开始,包括演示如何生成一个自定义视图引擎。下一步,我们将描述与内建的Razor视图引擎进行有效工作的技术。然后,我们将涉及如何生成和使用分部视图、子动作、以及Razor片段。这些都是有效的MVC开发的基本论题。

Creating a Custom View Engine
生成一个自定义视图引擎

We are going to start this chapter by diving in at the deep end. We’ll create a custom view engine. You don’t need to do this for most projects. The MVC Framework includes two built-in view engines that are full-featured and well-understood:
我们打算以深度潜入的方式来开始本章内容。我们将生成一个自定义视图引擎。对大多数项目,你不需要这么做。MVC框架包括了两个内建的视图引擎,它们是功能全面且便于理解的:

  • The Razor engine, the one we have been using in this book, was introduced with MVC version 3. It has a simple and elegant syntax, which we described in Chapter 5.
    Razor引擎,我们本书使用的引擎,是MVC版本3引入的。它具有简单而雅致的语法,我们在第5章已经作了描述。
  • The legacy ASPX engine (also known as the Web Forms view engine) uses the ASP.NET Web Forms <%...%> tag syntax. This engine is useful for maintaining compatibility with older MVC applications.
    遗留的ASPX引擎(也称为Web表单视图引擎)使用ASP.NET的Web表单的<%...%>标签语法。这个引擎对维护旧版的MVC应用程序是有用的。

The value in creating a custom view engine is to demonstrate how the request processing pipeline works and complete your knowledge of how the MVC Framework operates. This includes understanding just how much freedom view engines have in translating a ViewResult into a response to the client.
生成一个自定义视图引擎的价值是,演示请求处理管道如何工作,并完善你关于MVC框架如何操作的知识。这包括理解,视图引擎把一个ViewResult转换成对客户端的响应是多么自由。

View engines implement the IViewEngine interface, which is shown in Listing 15-1.
视图引擎实现IViewEngine接口,如清单15-1所示。

Listing 15-1. The IViewEngine Interface
清单15-1. IViewEngine接口

namespace System.Web.Mvc { public interface IViewEngine { ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache); ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache); void ReleaseView(ControllerContext controllerContext, IView view); } } 

The role of a view engine is to translate requests for views into ViewEngineResult objects. The first two methods in the interface, FindView and FindPartialView, are passed parameters that describe the request and the controller that processed it (a ControllerContext object), the name of the view and its layout, and whether the view engine is allowed to reuse a previous result from its cache. These methods are called when a ViewResult is being processed. The final method, ReleaseView, is called when a view is no longer needed.
视图引擎的作用,是把对视图的请求翻译成ViewEngineResult对象。这个接口中的前两个方法,FindView和FindPartialView,传递给它们的参数是,描述该请求及其处理控制器(一个ControllerContext对象)、视图名及其布局、以及是否允许视图引擎重用其缓存结果。这些方法在处理ViewResult被调用。最后一个方法,ReleaseView,在视图不再需要时调用。

■ Note The MVC Framework support for view engines is implemented by the ControllerActionInvoker class, which is the built-in implementation of the IActionInvoker interface, as described in Chapter 14. You won’t have automatic access to the view engines feature if you have implemented your own action invoker or controller factory.
注:MVC框架对视图引擎的支持是由ControllerActionInvoker类实现的,这是IActionInvoker接口的内建实现,如第14章所述。如果你已经实现了自己的动作调用器或控制器工厂,你将不能自动地访问这个视图引擎特性。

The ViewEngineResult class allows a view engine to respond to the MVC Framework when a view is requested. You express a result by choosing one of two constructors. If your view engine is able to provide a view for a request, then you create a ViewEngineResult using this constructor:
ViewEngineResult类允许在请求一个视图时,视图引擎对MVC框架作出响应。通过选择两个构造器之一,你可以发布一个结果。如果你的视图引擎能够对一个请求提供一个视图,那么你用以下构造器生成一个ViewEngineResult:

public ViewEngineResult(IView view, IViewEngine viewEngine) 

The parameters to this constructor are an implementation of the IView interface and a view engine (so that the ReleaseView method can be called later).
送给这个构造器的参数是IView接口的一个实现和一个视图引擎(以更随后能够调用ReleaseView方法)。

If your view engine can’t provide a view for a request, then you use this constructor:
如果你的视图引擎不能对一个请求提供一个视图,那么你使用这个构造器:

public ViewEngineResult(IEnumerable<string> searchedLocations) 

The parameter for this version is an enumeration of the places searched to find a view. This information is displayed to the user if no view can be found, as we’ll demonstrate later.
这个版本的参数是查找视图的一个位置枚举。如果找不到视图,这个信息会显示给用户,如我们稍后演示的那样。

■ Note You are not alone if you think that the ViewEngineResult class is a little awkward. Expressing outcomes using different versions of a class constructor is an odd approach and doesn’t really fit with the rest of the MVC Framework design. We don’t know why the designers took this approach, but we are, sadly, stuck with it—at least for the current version of the framework.
注:如果你认为这个ViewEngineResult类有点难用,你不是唯一的。使用一个类的不同版本的构造器来发布输出是一种奇怪的办法,而且并不真正符合MVC框架设计的其余部分。我们不知道为什么设计者要采用这种办法,但悲哀的是,我们被它缠住了 — 至少对当前的框架版本是这样。

The last building block of the view engine system is the IView interface, which is shown in Listing 15-2.
视图引擎系统的最后构造块是IView接口,如清单15-2所示。

Listing 15-2. The IView Interface
清单15-2. IView接口

namespace System.Web.Mvc { using System.IO; public interface IView { void Render(ViewContext viewContext, TextWriter writer); } } 

We pass an IView implementation to the constructor of a ViewEngineResult object, which is then returned from our view engine methods. The MVC Framework calls the Render method. The ViewContext parameter gives us information about the request from the client and the output from the action method. The TextWriter parameter is for writing output to the client.
我们把IView的一个实现传递给ViewEngineResult对象的一个构造器,然后它被我们的视图引擎方法所返回。MVC框架调用Render方法。ViewContext参数为我们给出了客户端请求的信息以及动作方法的输出。TextWriter参数用于把输出写给客户端。

As we said earlier, the simplest way to see how this works—how IViewEngine, IView, and ViewEngineResult fit together—is to create a view engine. We are going to create a simple view engine that returns one kind of view. This view will render a result that contains information about the request and the view data produced by the action method. This approach lets us demonstrate the way that view engines operate without getting bogged down in parsing view templates.
正如我们之前所说的,了解其如何工作最简单的办法 — IViewEngine、IView、以及ViewEngineResult如何组合在一起 — 是生成一个视图引擎。我们打算生成一个简单的视图引擎,它只返回一种视图。这个视图将呈现有关请求的信息以及动作方法产生的视图数据。这种办法使我们能够演示视图引擎的操作方式,而不必陷入解析视图模板的困境。

Creating a Custom IView
生成一个自定义IView

We are going to start by creating our implementation of IView. This will be the class that is responsible for generating the response to the client, as shown in Listing 15-3.
我们打算从生成我们的IView实现开始。这是负责生成客户端响应的一个类,如清单15-3所示。

Listing 15-3. A Custom IView Implementation
清单15-3. 一个自定义IView实现

using System.IO; using System.Web.Mvc; namespace Views.Infrastructure.CustomViewEngine { public class DebugDataView : IView { public void Render(ViewContext viewContext, TextWriter writer) { Write(writer, "---Routing Data---"); foreach (string key in viewContext.RouteData.Values.Keys) { Write(writer, "Key: {0}, Value: {1}", key, viewContext.RouteData.Values[key]); } Write(writer, "---View Data---"); foreach (string key in viewContext.ViewData.Keys) { Write(writer, "Key: {0}, Value: {1}", key, viewContext.ViewData[key]); } } private void Write(TextWriter writer, string template, params object[] values) { writer.Write(string.Format(template, values) + "<p/>"); } } } 

This view demonstrates the use of the two parameters to the Render method: we take values from the ViewContext and write a response to the client using the TextWriter.
这个视图演示了Render方法的两个参数的运用:我们取得ViewContext的值,并用TextWriter写出客户端响应。

Creating an IViewEngine Implementation
生成一个IViewEngine实现

Remember that the purpose of the view engine is to produce an ViewEngineResult object that contains either an IView or a list of the places searched for a suitable view. Now that we have an IView implementation to work with, we can create the view engine, which is shown in Listing 15-4.
记住,视图引擎的目的是产生一个ViewEngineResult对象,它含有一个IView或一个搜索适当视图的位置列表。现在,我们有了一个与之进行工作的IView实现,我们可以生成视图引擎了,如清单15-4所示。

Listing 15-4. A Custom IViewEngine Implementation
清单15-4. 一个自定义IViewEngine实现

using System.Web.Mvc; namespace Views.Infrastructure.CustomViewEngine { public class DebugDataViewEngine : IViewEngine { public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { if (viewName == "DebugData") { return new ViewEngineResult(new DebugDataView(), this); } else { return new ViewEngineResult(new string[] { "Debug Data View Engine" }); } } public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) { return new ViewEngineResult(new string[] { "Debug Data View Engine" }); } public void ReleaseView(ControllerContext controllerContext, IView view) { // do nothing } } } 

We are going to support only a single view, which is called DebugData. When we see a request for that view, we will return a new instance of our IView implementation, like this:
我们打算只支持一个单一的视图,其名为DebugData。当我们看到对这个视图的一个请求时,我们将返回我们的IView实现的一个新实例,像这样:

return new ViewEngineResult(new DebugDataView(), this); 

If we were implementing a more serious view engine, we would use this opportunity to search for templates, taking into account the layout and provided caching settings. As it is, our simple example requires us to create only a new instance of the DebugDataView class. If we receive a request for a view other than DebugData, we return a ViewEngineResult, like this:
如果我们实现一个更严格的视图引擎,我们将运用这个机会搜索模板、考虑布局、并提供缓存设置。实际上,我们的简单例子只需要我们生成一个新的DebugDataView类实例。如果我们接收的是对一个视图而不是对DebugData的请求,我们可以返回一个ViewEngineResult,像这样:

return new ViewEngineResult(new string[] { "Debug Data View Engine" }); 

The IViewEngine interface presumes that the view engine has places it needs to look to find views. This is a reasonable assumption, because views are typically template files that are stored as files in the project. In our case, we don’t have anywhere to look, so we just return a dummy location.
IViewEngine接口假定,视图引擎有它需要查找视图的地方。这是一个合理的假设,因为视图典型地是作为文件存储在项目中的模板文件。在我们的例子中,我们没有任何要查找的地方,因此我们只返回一个哑元位置。

Our custom view engine doesn’t support partial views, so we return a result from the FindPartialView method that indicates we don’t have a view to offer. We’ll return to the topic of partial views and how they are handled in the Razor engine later in the chapter. We have not implemented the ReleaseView method, since there are no resources that we need to release in our IView implementation, which is the usual purpose of this method.
我们的自定义视图引擎不支持分部视图,因此我们从FindPartialView方法返回一个结果,它指示我们不提供一个视图。我们将在本章的后面,回到分部视图以及如何在Razor引擎中处理它们这一论题。我们还没有实现ReleaseView方法,因为,在我们的IView实现中不存在我们需要释放的资源,释放资源通常是这一方法的目的。

Registering a Custom View Engine
注册一个自定义视图引擎

There are a couple of different ways to register a custom view engine. The first is to do so in the Application_Start method of Global.asax, as shown in Listing 15-5.
注册一个自定义视图引擎有两种不同的方式。第一个是在Global.asax的Application_Start方法做这件事,如清单15-5所示。

Listing 15-5. Registering a Custom View Engine Using Global.asax
清单15-5. 用Blobal.asax注册一个自定义视图

protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ViewEngines.Engines.Add(new DebugDataViewEngine()); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } 

The static ViewEngine.Engines collection contains the set of view engines that are installed in the application. The MVC Framework supports the idea of there being several engines installed in a single application. When a ViewResult is being processed, the action invoker obtains the set of installed view engines and calls their FindView methods in turn.
静态的ViewEngine.Engines集合含有一组在应用程序中安装的视图引擎。MVC框架支持在一个单一的应用程序中安装几个引擎的思想。当处理一个ViewResult时,动作调用器获取一组已安装的视图引擎,并依次调用它们的FindView方法。

The action invoker stops calling FindView methods as soon as it receives a ViewEngineResult object that contains an IView. This means that the order in which engines are added to the ViewEngines.Engines collection is significant if two or more engines are able to service a request for the same view name. If you want your view to take precedence, then you can insert it at the start of the collection, like this:
一旦动作调用器接收到一个含有IView的ViewEngineResult对象,便会停止调用FindView方法。如果有两个或多个引擎能够对同视图名的请求进行服务,这意味着被添加到ViewEngines.Engines集合中引擎的顺序是重要的。如果你希望你的视图取得优先,那么你可以把它插入在这个集合的开始部分,像这样:

ViewEngines.Engines.Insert(0, new DebugDataViewEngine()); 

An alternative approach is to use the application-wide dependency resolver. When the application starts, the dependency resolver will be asked for any available implementations of the IViewEngine interface. If we were using the NinjectDependencyResolver class we introduced in Chapter 10, we could register our view engine in the AddBindings method of that class, as follows:
另一种方式是采用应用程序范围的依赖性解析器。当应用程序启动时,依赖性解析器将找出各种可用的IViewEngine接口实现。如果我们使用的是第10章介绍的NinjectDependencyResolver类,我们就可以在这个类的AddBindings方法中注册我们的视图引擎,如下所示:

private void AddBindings() { // put bindings here Bind<IViewEngine>().To<DebugDataViewEngine>(); } 

You have no control over the order in which the view engines are processed when using this approach, so it is best to use the ViewEngines.Engines collection if you are concerned that view engines will be competing with each other.
采用这种方式时,你不能对处理视图引擎的顺序加以控制,因此,如果你关注视图引擎的彼此竞争,最好还是使用ViewEngines.Engines集合。

We are now in a position to test our view engine. We created a project with a HomeController class and defined the Index action method as follows:
现在我们到了检测这个视图引擎的时候了。我们生成一个带有HomeController类的项目,并按如下代码定义了Index动作方法:

using System; using System.Web.Mvc; namespace Views.Controllers { public class HomeController : Controller { public ActionResult Index() { ViewData["Message"] = "Hello, World"; ViewData["Time"] = DateTime.Now.ToShortTimeString(); return View("DebugData"); } } } 

The important part is that the action method uses the View method to return a ViewResult that specifies the DebugData view. If you run the application, the default route that Visual Studio creates will invoke our action method, which will lead to our view engine being used. The result is shown in Figure 15-1.
重要的部分是,这个动作方法使用了View方法来返回一个指定了DebugData视图的ViewResult。如果你运行这个应用程序,Visual Studio生成的默认路由将调用我们的动作方法,这将导致使用我们的视图引擎。其结果如图15-1所示。

图15-1

Figure 15-1. The output from a custom view engine
图15-1. 自定义视图引擎的输出

This is the result of our FindView method being called for a view that we are able to process. We can see the effect of our dummy location if we change the Index method so that the ViewResult requests a view that none of the installed view engines can respond to, like this:
这是我们的FindView方法对一个我们能够处理的视图进行调用所产生的结果。如果我们修改这个Index方法,以使ViewResult请求一个已安装视图引擎不能作出响应的视图,我们可以看到哑元位置的结果,像这样:

return View("No_Such_View"); 

When we run the application, the action invoker goes to each of the available view engines and calls its FindView method. None of them will be able to service this request, and each will return the set of locations where they looked. You can see this effect in Figure 15-2.
当我们运行这个应用程序时,动作调用器进入每一个可用的视图引擎,并调用它的FindView方法。它们谁也不能对这个请求进行服务,每一个都将返回一组它们所查找的位置。你可以在图15-2中看到这个结果。

图15-2

Figure 15-2. The error when no view engine can service a request
图15-2. 无视图引擎对请求进行服务时产生的错误

You’ve seen this error before, of course, but notice that our dummy location has been added to the list of locations that are reported as part of the error.
之前你已经看到过这种错误,但要注意,我们的哑元位置已经被添加到该错误报告的位置列表上了。

USING THIRD-PARTY VIEW ENGINES
使用第三方视图引擎

A number of view engines are available in addition to the ones that come built in to the MVC Framework. And fortunately, they are all much more richly featured than the one we have created in this chapter.
除了MVC框架内建的视图引擎之外,还有许多视图引擎是可用的。幸运的是,它们都比我们本章所建的这个引擎具有丰富得多的特性。

Four of the most popular third-party view engines are Spark, NHaml, Brail, and NVelocity. Some of these are ports of view engines from other languages or web platforms. NVelocity is a port of the Java Apache Velocity template engine, and NHaml is a port of the Ruby on Rails Haml engine. The most popular, Spark, is particularly interesting in that it allows programming logic to be expressed in the flow of HTML elements, which can make templates more readable.
四个最流行的第三方视图引擎是Spark、NHaml、Brail、和NVelocity。其中一些是来自其它语言或web平台的视图引擎端口。NVelocity是Java Apache Velocity的模板引擎端口,而NHaml是Ruby on Rails的Haml引擎端口。最流行的Spark,是特别有意思的,它允许把编程逻辑表示成HTML元素流,这可以使模板更加可读。

We have not spent any significant time using these alternative engines, but we know some good programmers who swear by them. If the Razor or ASPX engine isn’t what your project needs, you might find what you are looking for in one of these other engines.
我们没有花太多时间使用这些可选引擎,但我们知道,一些优秀程序员信赖它们。如果Razor或ASPX引擎不是你项目所需要的,你也许会发现,其中之一就是你要找的另一种引擎。

Working with the Razor Engine
与Razor引擎一起工作

In the previous section, we were able to create a custom view engine by implementing just two interfaces. Admittedly, we ended up with something very simple that generated very ugly views, but you saw how the concept of MVC extensibility continues throughout the request processing pipeline.
在上一小节中,只要通过实现两个接口,我们便能够生成一个自定义视图引擎。诚然,在我们生成非常丑陋的视图这样一些十分简单的事情时便停下来了,但你已经看到了MVC的可扩展性概念在整个请求处理管道上的延续。

The complexity in a view engine comes from the system of view templates that include code fragments, support layouts, and are compiled to optimize performance. We didn’t do any of these things in our simple custom view engine, and we didn’t need to, because the Razor engine takes care of all of that for us. The functionality that almost all MVC applications require is available in Razor. Only a vanishingly small number of projects need to go to the trouble of creating a custom view engine.
视图引擎的复杂性来自于视图模板系统,这包括代码片段、支持布局、以及编译等,以达到优化性能。在我们简单的自定义视图引擎中没有做这些方面的任何事情,而且我们也不需要做这些,因为Razor引擎为我们照顾着方方面面。几乎所有MVC应用程序所需要的功能性,在Razor中都是可用的。只有十分稀少的项目需要挑战生成自定义视图的麻烦。

As a reminder, Razor is the view engine that was introduced with MVC 3 and replaces the previous view engine (known as the ASPX or Web Forms engine). You can still use the ASPX view engine, and Microsoft has not said that it is deprecating that engine, but our feeling is that the future of view engines in MVC is clearly Razor.
作为一个提醒,Razor是随MVC 3引入的视图引擎,它取代了以前的视图引擎(称为ASPX,或Web表单引擎)。你仍然可以使用ASPX视图引擎,而且微软也没说这已经是不赞成引擎,但我们的感觉是,MVC引擎的未来显然是Razor。

We gave you a primer of the new Razor syntax in Chapter 5. In this chapter, we are going to show you how to apply that syntax and other features to create and render Razor views. You’ll also learn how to customize the Razor engine.
在第5章中,我们给出了新型Razor的初步语法。本章中,我们打算向你演示如何运用这些语法,以及生成和呈现Razor视图的一些其它特性。你也将学习如何定制Razor引擎。

Understanding Razor View Rendering
理解Razor视图的呈现

The Razor View Engine compiles the views in your applications to improve performance. The views are translated into C# classes, and then compiled, which is why you are able to include C# code fragments so easily. It is instructive to look at the source code that Razor views generate, because it helps to put many of the Razor features in context. Listing 15-6 shows a simple Razor view that takes an array of strings as the view model object.
Razor视图引擎会编译你应用程序中的视图,以改善性能。视图被翻译成C#类,然后被编译,这是为什么你能够如此容易地在视图中包含C#代码片段的原因。查看Razor视图生成的源代码是有意义的,因为这有助于把许多Razor特性放到上下文情景之中。清单15-6显示了一个简单的Razor视图,它以一个字符串数组作为视图模型对象。

Listing 15-6. A Simple Razor View
清单15-6. 一个简单的Razor视图

@model string[] @{ ViewBag.Title = "Index"; } This is a list of fruit names:
@foreach (string name in Model) { <span><b>@name</b></span> }

The views in an MVC application are not compiled until the application is started, so to see the classes that are created by Razor, you need to start the application and navigate to an action method. Any action method will do, since the initial request to an MVC application triggers the view compilation process.
直到应用程序启动之前,MVC应用程序中的视图这会被编译,因此,要查看Razor生成的类,你需要启动应用程序,并导航到一个动作方法。任何动作方法都行,因为,发送到MVC应用程序的最初请求就会触发视图编译过程。

Conveniently, the generated classes are written to the disk as C# code files and then compiled, which means that you can see the C# statements that represent a view. You can find the generated files in c:\Users\yourLoginName\AppData\Local\Temp\Temporary ASP.NET Files on Windows 7, or the equivalent folder on your operating system version and installation location.
出于方便,所生成的类被写成磁盘上的C#代码文件,并随后被编译,这意味着你可以看到表示一个视图的C#语句。你可以在Windows 7的c:\Users\yourLoginName\AppData\Local\Temp\Temporary ASP.NET Files文件夹,或其它版本的操作系统的相应文件夹中看到这种生成的文件。

Finding the code file generated for a particular view requires a bit of poking around. There are usually a number of folders with cryptic names, and the names of the .cs files don’t correspond to the names of the classes they contain. As an example, we found the generated class for the view in Listing 15-6 in a file called App_Web_gvaxronl.1.cs in the root\83e9350c\e84cb4ce folder. We have tidied up the class from our system to make it easier to read, as shown in Listing 15-7.
找到为特定视图生成的这种代码文件需要一点盲目点击。有许多通常为隐藏名的文件夹,而且.cs文件的名字与它们所包含的类名不对应。例如,我们发现,清单15-6的视图所生成的类位于root\83e9350c\e84cb4ce文件夹的名为App_Web_gvaxronl.1.cs的文件中。我们已经根据我们的系统对这个类作了一些整理,以使它便于阅读,如清单15-7所示。

Listing 15-7. The Generated C# Class for a Razor View

Namespace ASP { using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Web; using System.Web.Helpers; using System.Web.Security; using System.Web.UI; using System.Web.WebPages; using System.Web.Mvc; using System.Web.Mvc.Ajax; using System.Web.Mvc.Html; using System.Web.Routing; public class _Page_Views_Home_Index_cshtml : System.Web.Mvc.WebViewPage<string[]> { public _Page_Views_Home_Index_cshtml() { } public override void Execute() { WriteLiteral("\r\n"); ViewBag.Title = "Index"; WriteLiteral("\r\nThis is a list of fruit names:\r\n\r\n"); foreach (string name in Model) { WriteLiteral("<span><b>"); Write(name); WriteLiteral("</b></span>\r\n"); } } } } 

First, note that the class is derived from WebViewPage<T>, where T is the model type. This is how strongly typed views are handled. Also notice the name of the class that has been generated. You can see how the path of the view file has been encoded in the class name. This is how Razor maps requests for views into instances of compiled classes.
首先,注意这个类派生于WebViewPage<T>,这里的T是视图中model的类型。这是如何处理强类型视图的。另外,要注意所生成的这个类名。你可以看出,视图文件的路径是如何编码为类名的。这是Razor如何把对视图的请求映射到编译类的实例中去的。

In the Execute method, you can see how the statements and elements in the view have been handled. The code fragments that we prefixed with the @ symbol are expressed directly as C# statements.
在这个执行方法中,你可以看出如何处理视图中的语句和元素。我们以@符号作为前缀的代码片段,被直接表示成了C#语句。

The HTML elements are handled with the WriteLiteral method, which writes the contents of the parameter to the result as they are given. This is opposed to the Write method, which is used for C# variables and encodes the string values to make them safe for use in an HTML page.
HTML元素以WriteLiteral方法进行处理,它把参数的内容写成它们给出的结果。这与Write方法相反,它用于C#变量,并对字符串值进行编码,以使它们安全地用于HTML页面。

Both the Write and WriteLiteral methods write content to a TextWriter object. This is the same object that is passed to the IView.Render method, which you saw at the start of the chapter. The goal of a compiled Razor view is to generate the static and dynamic content and send it to the client via the TextWriter. This is useful to keep in mind when we look at HTML helper methods later in the chapter.
Write和WriteLiteral两个方法都是把内容写到一个TextWriter对象。这是传递给IView.Render方法的同一个对象,你在本章开始部分看到过它。编译Razor视图的目的是生成静态和动态内容,并通过TextWriter把它发送给客户端。请把它记住,这对我们本章后面部分考查HTML辅助方法时是有用的。

Adding Dependency Injection to Razor Views
将依赖性注入添加到Razor视图

Every part of the MVC request processing pipeline supports DI, and the Razor View Engine is no exception. However, the technique is different from what we have shown before, because we are spanning the boundary between classes and views.
MVC请求处理管道的每个部分都支持DI,Razor视图引擎也不例外。然而,其技术与我们之前所演示的不同,因为我们是在跨越类与视图之间的界限。

Let’s imagine that we have an interface for a simple calculator feature that will sum two numeric values. Our interface for this is called ICalculator, as shown in Listing 15-8.
让我们想象,我们有一个简单计算机特性的接口,它将求取两数之和。该接口叫做ICalculator,如清单15-8所示。

Listing 15-8. The ICalculator Interface
清单15-9. ICalculator接口

namespace Views.Models { public interface ICalculator { int Sum(int x, int y); } } 

If we want a view to be able to use the ICalculator interface and have the implementation injected, then we need to create an abstract class that is derived from WebViewPage, as shown in Listing 15-9.
如果我们希望一个视图能够使用这个ICalculator接口,并具有这个实现的注入,那么,我们需要生成一个派生于WebViewPage的抽象类,如清单15-9所示。

Listing 15-9. Deriving a Class from WebViewPage
清单15-9. 从WebViewPage派生一个类

using System.Web.Mvc; using Ninject; namespace Views.Models.ViewClasses { public abstract class CalculatorView : WebViewPage { [Inject] public ICalculator Calulator { get; set; } } } 

The simplest way to support DI is to use property injection, where our DI container injects dependencies into a property, rather than into the constructor. To do this, we must annotate the property we want injected with the Inject attribute, as the listing demonstrates. To take advantage of this in a view, we use the Razor inherits element, as shown in Listing 15-10.
支持DI最简单的方式是使用属性注入,在这里,我们的DI容器把依赖性注入到一个属性,而不是注入到构造器。为了做这件事,我们必须用Inject性质对我们想要注入的属性进行注释,如清单所示。为了在视图中利用这个属性,我们要使用Razor的inherits元素,如清单15-10所示。

Listing 15-10. Specifying Inheritance in a Razor View
清单15-10. 在一个Razor视图中指定继承

@inherits Views.Models.ViewClasses.CalculatorView @{ ViewBag.Title = "Calculate"; } <h4>Calculate</h4> The calculation result for @ViewBag.X and @ViewBag.Y is @Calulator.Sum(ViewBag.X, ViewBag.Y) 

We can use @inherits to specify the base class we create in Listing 15-7. This gives us access to the Calculator property, which we are then able to get in order to receive an implementation of the ICalculator interface, all without creating a dependency between the view and the ICalculator implementation.
我们可以用@inherits来指定我们在清单15-7中生成的基类。这使我们可以对Calculator属性进行访问,这是我们随后接收ICalculator接口的实现才能够得到的,所有这些都没有生成视图与ICalculator实现之间的依赖性。

The effect of the inherits tag is to change the base for the generated class. The effect of the inherits tag in Listing 15-10 is that the generated class for the view is defined like this:
inherits标签的作用是改变生成类的基类。清单15-10中inherits标签的作用是,该视图的生成类是这样定义的:

public class _Page_Views_Home_Calculate_cshtml : Views.Models.ViewClasses.CalculatorView { ... } 

Since our generated class is now derived from our abstract class, we have access to the Calculator property we defined. All that remains is to register our implementation of the ICalculator interface with the dependency resolver, so that Ninject will be used to create the view class and have the opportunity to perform the property injection. We do this in the AddBindings method of our NinjectDependencyResolver class, as follows:
由于我们的生成类现在派生于我们的抽象类,我们可以访问我们定义的Calculator属性。剩下来事就是,用依赖性解析器来注册我们的ICalculator接口实现了,以便用Ninject来生成这个视图类,并有机会执行属性注入。我们在NinjectDependencyResolver类的AddBindings方法中来做这件事,如下所示:

private void AddBindings() { // put bindings here Bind<ICalculator>().To<SimpleCalculator>(); } 

And just like that, we have DI for Razor views.
就这样,我们实现了Razor视图的DI。

Configuring the View Search Locations
配置视图搜索位置

The Razor View Engine follows the convention established in earlier versions of the MVC Framework when looking for a view. For example, if you request the Index view associated with the Home controller, Razor looks through this list of views:
在查找一个视图时,Razor视图引擎遵循了MVC框架早期版本建立起来的约定。例如,如果你请求与Home控制器相关的Index视图,Razor会审查这样的视图列表:

  • ~/Views/Home/Index.cshtml
  • ~/Views/Home/Index.vbhtml
  • ~/Views/Shared/Index.cshtml
  • ~/Views/Shared/Index.vbhtml

As you now know, Razor isn’t really looking for the view files on disk, because they have already been compiled into C# classes. Razor looks for the compiled class that represents these views. The .cshtml files are templates containing C# statements (the kind we are using), and the .vbhtml files contain Visual Basic statements.
正如你现在知道的,Razor实际上不会在磁盘上查找这些视图文件,因为它们还没有被编译成C#类。Razor查找的是表示这些视图的编译类。.cshtml文件是含有C#语句的模板(我们正在使用的这种),而.vbhtml文件含有Visual Basic语句。

You can change the view files that Razor searches for by creating a subclass of RazorViewEngine. This class is the Razor IViewEngine implementation. It builds on a series of base classes that define a set of properties that determine which view files are searched for. These properties are described in Table 15-1.
你可以通过生成一个RazorViewEngine子类,来改变Razor搜索的这种视图文件。这个类是Razor的IViewEngine实现。它建立于一组基类之上,这些类定义一组用来确定搜索哪种视图文件的属性。这些属性如表15-1所描述。

Table 15-1. Razor View Engine Search Properties
表15-1. Razor视图引擎搜索属性
Property
属性
Description
描述
Default Value
默认值
ViewLocationFormats
MasterLocationFormats
PartialViewLocationFormats
The locations to look for views, partial views, and layouts
查找视图、分部视图、以及布局的位置
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
AreaViewLocationFormats
AreaMasterLocationFormats
AreaPartialViewLocationFormats
The locations to look for views, partial views, and layouts for an area
查找一个区域的视图、分部视图、及布局的位置
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml"

These properties predate the introduction of Razor, which why each set of three properties has the same values. Each property is an array of strings, which are expressed using the composite string formatting notation. The following are the parameter values that correspond to the placeholders:
这些属性先于Razor的引入,这是每组三个属性具有相同值的原因。每个属性是一个字符串数组,它们是用复合字符串格式化符号来表示的。以下是与占位符对应的参数值:

  • {0} represents the name of the view.
    {0} 表示视图名
  • {1} represents the name of the controller.
    {1} 表示控制器名
  • {2} represents the name of the area.
    {2} 表示区域名

To change the search locations, you create a new class that is derived from RazorViewEngine and change the values for one or more of the properties described in Table 15-1. Listing 15-11 demonstrates a replacement view engine that changes the name of the folder used for shared views and looks only for C# Razor templates (which have the .cshtml file name extension).
为了修改搜索位置,你要生成一个派生于RazorViewEngine的新类,并修改表15-1所描述的一个或多个属性值。清单15-11演示了一个替换的视图引擎,它修改了共享视图文件夹名,并且只查找C#的Razor模板(其文件扩展名为.cshtml)。

Listing 15-11. Changing the Search Locations in the Razor View Engine

using System.Web.Mvc; namespace Views.Infrastructure { public class CustomRazorViewEngine : RazorViewEngine { public CustomRazorViewEngine() { ViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/Common/{0}.cshtml" }; } } } 

We have set a new value for the ViewLocationFormats. Our new array contains entries only for .cshtml files. In addition, we have changed the location we look for shared views to be Views/Common, rather than Views/Shared.
我们已经对ViewLocationFormats设置了一个新值。我们的新数组只包含.cshtml文件的条目。此外,我们已经把查找共享视图的位置修改为View/Common,而不是Views/Shared。

We register our derived view engine using the ViewEngines.Engines collection in the Application_Start method of Global.asax, like this:
我们在Global.asax的Application_Start方法中,用ViewEngines.Engines集合来注册我们的这个派生视图引擎,像这样:

protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new CustomRazorViewEngine()); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } 

Remember that the action invoker goes to each view engine in turn to see if a view can be found. By the time that we are able to add our view to the collection, it will already contain the standard Razor View Engine. To avoid competing with that implementation, we call the Clear method to remove any other view engines that may have been registered, and then call the Add method to register our custom implementation.
记住,动作调用器依次进入每个视图引擎,以查看是否能找到一个视图。到此,我们能够把我们的视图添加到这个集合,它将包含标准的Razor视图引擎。为了避免与这个实现竞争,我们调用Clear方法来删除可能已经被注册的其它引擎,然后调用Add方法来注册我们的自定义实现。

Adding Dynamic Content to a Razor View
对Razor视图添加动态内容

The whole purpose of views is to allow you to render parts of your domain model as a user interface. To do that, you need to be able to add dynamic content to views. Dynamic content is generated at runtime, and can be different for each and every request. This is opposed to static content, such as HTML, which you create when you are writing the application and is the same for each and every request.
视图的全部目的是,让你把域模型的一部分内容呈现成用户界面。为了做这种事,你需要能够对视图添加动态内容。动态内容是在运行时生成的,并且,对于每个请求,其内容是不同的。这与静态内容,比如HTML,恰好相反。在你编写应用程序时,其内容就已经生成了,而且对每次请求其内容都是相同的。

You can add dynamic content to views in the four ways described in Table 15-2.
你可以以四种方式把动态内容添加到视图,如表15-2所示。

Table 15-2. Adding Dynamic Content to a View
表15-2. 对视图动态内容
Technique
技术
When to Use
什么时候使用
Inline code
内嵌代码
Use for small, self-contained pieces of view logic, such as if and foreach statements. This is the fundamental tool for creating dynamic content in views, and some of the other approaches are built on it.
用于小型的、自包含视图逻辑的片段,如if和foreach语句。这是在视图中生成动态内容的基本手段,也是一些其它办法的基础。
HTML helper methods
HTML辅助方法
Use to generate single HTML elements or small collections of them, typically based on view model or view data values. The MVC Framework includes a number of useful HTML helper methods, and it is easy to create your own. As we’ll show you later in the chapter, any method that returns an MvcHtmlString object can be an HTML helper method.
用于生成一个独立的HTML元素或元素集合,典型地,是基于视图模型或视图数据值。MVC包含了许多有用的HTML辅助方法,而且,生成你自己的辅助方法也很容易。正如我们本章稍后要给你演示的,返回MvcHtmlString对象的任何方法都可以是一个HTML辅助方法。
Partial views
分部视图
Use for sharing subsections of view markup between views. Partial views can contain inline code, HTML helper methods, and references to other partial views. Partial views don’t invoke an action method, so they can’t be used to perform business logic.
用于在视图之间共享的视图标记子片段。分部视图可以含有内嵌代码、HTML辅助方法、以及引用其它分部视图。分部视图不调用动作方法,因此它们不能用来执行事务逻辑。
Child actions
子动作
Use for creating reusable UI controls or widgets that need to contain business logic. When you use a child action, it invokes an action method, renders a view, and injects the result into the response stream.
用于生成可重用的UI控件,或需要含有事务逻辑的小部件。当你使用一个子动作时,它调用一个动作方法、返回一个视图、并把结果注入到响应流中。

Using Inline Code
使用内嵌代码

The simplest and easiest way to generate dynamic content is to use inline code—one or more C# statements prefixed with the @ symbol. This is the heart of the Razor View Engine, and we have used this technique in almost all of the examples so far in this book.
生成动态内容最简单、且最容易的办法是,使用内嵌代码 — 用@符号做前缀的一条或多条C#语句。这是Razor视图引擎的核心,而且,我们在本书几乎所有的例子中都使用了这种技术。

Chapter 5 covers the Razor syntax, and we are not going to repeat that information in this chapter. Instead, we’ll demonstrate some advanced features that will make sense now that we have explained more of the details about request processing and view engines.
第5章涉及了Razor语法,因而我们不打算在本章重复这些信息。不过,我们将演示一些高级特性,在我们已经较详细地解释了关于请求处理和视图引擎之后,现在做这些演示是有意义的。

INLINE CODE AND THE SEPARATION OF CONCERNS
内嵌代码与关注分离

If you have come to the MVC Framework from ASP.NET Web Forms, you might be wondering why we are so keen on inline code. After all, the convention in Web Forms is to put as much code as possible in the code-behind files. It is easy to be confused, especially as the reason for relying on code-behind files is often to maintain separation of concerns.
如果你是从APS.NET的Web表单转到MVC框架上来的,你可能会奇怪,为什么我们这么喜爱内嵌代码。毕竟,Web表单中的约定是,把尽可能多的代码放到后台代码文件中去。这很容易让人困惑,特别地,后台代码文件的动机,就是维持关注分离。

The difference arises because ASP.NET MVC and ASP.NET Web Forms have different notions about which concerns should be separated and where the dividing line should be. Web Forms separates declarative markup and procedural logic. The ASPX files contain the markup, and the code-behind files contain the logic. By contrast, the MVC Framework separates presentation logic and application logic. Controllers and domain models are responsible for application and domain logic, and views contain presentation logic. So, inline code in MVC views fits nicely within the MVC architectural framework. The Razor syntax makes it easy to creating maintainable and extensible views that transform domain model entities into UI components.
差别出现了,因为ASP.NET MVC与ASP.NET Web表单对如何关注分离以、及如何划分有不同的观念。Web表单是对说明性标记和处理逻辑进行分离。ASPX文件含有标记,而后台代码文件含有逻辑。相反地,MVC框架是对表现逻辑和应用程序逻辑进行分离。控制器与域模型负责应用程序和域逻辑,而视图含有表现逻辑。因此,MVC视图中的内嵌代码恰好适合于MVC的结构框架。Razor语法使其易于生成、维护、和扩展的视图,把域模型实体转换到UI组件之中。

That said, the flexibility of the inline code feature makes it easy to blur the boundaries between the components of your application and let the separation of concerns break down. A degree of discipline is required to maintain effective separation of concerns in a way that is consistent with the MVC design pattern. We suggest that as you start out with the MVC Framework, you focus on understanding the features and functions available. As you become more experienced, you’ll develop a feel for the natural division of responsibility between views and the other MVC components.
这就是说,这种内嵌代码特性的灵活性,容易模糊应用程序组件之间的界限,并打破关注分离。这就需要运用有度,以一种与MVC设计模式相应的方式来维持关注分离。我们建议,当你开始从事MVC框架时,你的重点是理解这些可用的特性和功能。随着你积累一定的经验,你将摸索出自然地划分视图与其它MVC组件职责的感觉。

Importing Namespaces into a View
将命名空间引入视图

Razor views are compiled with a set of using statements for commonly used namespaces, sparing you from needing to specify the namespace when you want to use popular classes. You can see the set of namespaces that are imported in Listing 15-7, shown earlier in the chapter. Unfortunately, the list of namespaces doesn’t include the ones in our project that we are likely to use. Listing 15-12 shows an example of referring to a class that is not on that list.
被编译的Razor视图带有一组常规使用的命名空间的using语句,因此,在你使用这些常规类时,不需要指定命名空间。你可以在本章前面演示的清单15-7中看到这组引入的命名空间。不幸的是,这组命名空间列表并未在项目中包含我们喜欢使用的一些空间。清单15-12演示了引用不在这个列表中的类的例子。

Listing 15-12. Referring to a Class By Its Fully Qualified Name
清单15-12. 通过全限定名引用一个类

@{ ViewBag.Title = "Index"; } Here is some data: @DynamicData.Infrastructure.MyUtility.GetUsefulData() 

In this example, we have a class called MyUtility, which has a method called GetUsefulData that we want to invoke from our view. We have referred to the class by its fully qualified name because the namespace that contains the class, DynamicData.Infrastructure, is not one of those that are automatically added to the generated class when the view is compiled.
在这个例子中,我们有一个名为MyUtility的类,它有一个我们想在视图中调用的名为GetUseFullData的方法。我们用全限定名引用了这个类,因为,在视图被编译时,包含这个类的命名空间,DynamicData.Infrastructure,不是自动地被添加到生成类中的一个命名空间。

Typing in all of the namespace elements like this becomes laborious, and it makes views harder to read. Fortunately, we are able to add using statements to the generated classes that are produced from views, as described in the following sections.
像这样输入全限定命名空间元素,变得费力,而且使视图难于阅读。幸运的是,我们能够象以下几节所描述的那样,把using语句添加视图所产生的生成类中。

Adding a @using Tag to a View
对视图添加一个@using标签

The easiest way to import a namespace is to use the @using tag in the Razor view. This works just like a regular C# using statement. Listing 15-13 provides an illustration.
引入命名空间最容易的方式是在Razor视图使用@using标签。它的作用就像常规C#的using语句一样。清单15-13提供了一个演示。

Listing 15-13. Adding a @using Tag to a View
清单15-13. 将@using添加到一个视图

@using DynamicData.Infrastructure @{ ViewBag.Title = "Index"; } Here is some data: @MyUtility.GetUsefulData() 

When the view is compiled, the namespace that we have specified is imported using a C# using statement. This allows you to refer to classes, such as MyUtility in this example, without needing to qualify the class name with the namespace.
当这个视图被编译时,我们已经指定的这个命名空间是用一条C#的using语句引入的。这允许你引用所需要的类,如这个例子中的MyUtility,而不需要用命名空间限定这个类名。

Adding a Namespace to Web.config
将一个命名空间添加到Web.config

A @using tag affects only one view. If you want to import a namespace to all of the views in a project, you can add the namespace to the Views/Web.config file, as shown in Listing 15-14.
一个@using标签仅影响一个视图。如果你想把一个命名空间引入到项目中的所有视图,你可以将这个命名空间添加到Views/Web.config文件,如清单15-14所示。

Listing 15-14. Adding a Namespace to the Web.config File
清单15-14. 将一个命名空间添加到Web.config文件

<system.web.webPages.razor> <pages pageBaseType="System.Web.Mvc.WebViewPage"> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing" /> <add namespace="DynamicData.Infrastructure"/> </namespaces> </pages> </system.web.webPages.razor> 

■ Caution This is the Web.config file that is in the Views folder, and not the main Web.config file for the project.
注意:这是在Views文件夹中的Web.config文件,而不是项目的主Web.config文件。

Adding a namespace to the Views/Web.config file has the same effect as using an @using tag, but means you don’t need to import the same namespaces into all of your views.
把一个命名空间添加到Views/Web.config文件具有与@using标签同样的效果,但这意味着你不需要在所有视图中都引入同样的命名空间。

Understanding Razor HTML String Encoding
理解Razor的HTML字符串编码

Cross-site scripting (XSS) is a kind of attack on web sites. Put simply, the attacker puts malicious scripts into the input elements of an application’s HTML forms in the hope that they will be displayed to another user. This can allow the attacker to take over another user’s account or take actions on the victim’s behalf. We show you an example of an XSS attack in Chapter 21.
网站脚本(Cross-site scripting — XSS)是一种对web网站的攻击。简单地说,攻击者把恶意脚本放入应用程序的一个HTML表单中的input元素,以希望把它们显示给另一用户。这可以使攻击者能够从其它地方获取另一个用户的账号,或者对受害人的利益采取一些动作。我们将在第21章向你演示一个XSS攻击的例子。

Razor helps protect against XSS by automatically encoding the output from any @ tag to make it safe to display. This means that characters that have special meaning in HTML are replaced by their escaped equivalent. For example, the < character is replaced with &lt;.
通过自动地对@标签的输出进行编码以安全显示,Razor有助于防止XSS。这意味着,具有特定含义的HTML字符被它们的转义字符所代替。例如,<字符用&lt;来代替。

As a simple example, imagine that the GetUsefulData method in our MyUtility class from the earlier listings returns a string that is actually a script. Listing 15-15 shows our problem class. Imagine that this method returns a string that a user had entered into a form earlier.
作为一个例子,假设前面清单的MyUtility类中的GetUsefulData方法返回的字符串实际上是一个脚本。清单15-15显示了我们的问题类。假设这个方法返回了一个用户早前在表单中输入的一个字符串:

Listing 15-15. A Class That Returns a Script
清单15-15. 返回一个脚本的类

namespace DynamicData.Infrastructure { public class MyUtility { public static string GetUsefulData() { return "<form>Enter your password:<input type=text> <input type=submit value=\"Log In\"/></form>"; } } } 

This is a trivial example—real XSS attacks can be extremely subtle and tend not to change the appearance of the page—but the problem is obvious. If we were to take the output from the GetUsefulData method and inject it into our response, the browser would treat it as part of the regular HTML of the page. This allows users to be presented with a simple form that we didn’t want them to see.
这是一个价值不高的例子 — 实际的XSS攻击可能相当狡猾,而且不会修改页面的外观 — 但问题是显然的。如果我们要取得GetUsrfulData方法的输出,并把它注入到我们的响应。浏览器会把它视为页面常规HTML的一部分。这就把一个我们并不想让用户看到的表单显示给用户。

Fortunately, when we use the @ tag to call a method or read a variable, the result is encoded. The encoded HTML that the MVC Framework generates for the method result is as follows:
幸运的是,当我们用@标签来调用一个方法或读取一个变量时,其结果是被编码的。MVC框架对这个方法结果所生成的编码HTML如下所示:

Here is some data: &lt;form&gt;Enter your password:&lt;input type=text&gt;&lt;input type=submit value=&quot;Log In&quot;/&gt;/form&gt; 

You can see how this is rendered in the browser in Figure 15-3.
你可以在图15-3中看到这是如何呈现的。

图15-3

Figure 15-3. Safely encoded HTML
图15-3. 被安全编码的HTML

You don’t need to take any explicit steps to ensure that your data is encoded, because Razor does it by default.
你不需要采取任何明确的步骤来确保你的数据被编码,因为Razor会默认地做这项工作。

If you want to disable automatic encoding, a couple of options are available. The first is to return an instance of MvcHtmlString as the result of the method or property. This type represents an HTML string that doesn’t require any further encoding. Listing 15-16 shows how we can apply this to our MyUtility class.
如果你想取消自动编码,有两个选项可用。第一个是返回一个MvcHtmlString实例,作为该方法或属性的结果。这个类型会表现一个不作任何进一步编码的HTML。清单15-16演示了我们可以如何把它运用于我们的MyUtility类。

Listing 15-16. Using the MvcHtmlString Class to Bypass Content Encoding
清单15-16. 使用MvcHtmlString类来绕过内容编码

using System.Web.Mvc; namespace DynamicData.Infrastructure { public class MyUtility { public static MvcHtmlString GetUsefulData() { return new MvcHtmlString("<form>Enter your password:<input type=text> <input type=submit value=\"Log In\"/></form>"); } } } 

The string value is passed to the MvcHtmlString object as a constructor parameter and will be injected into the response sent to the client as is. You can see the effect of this change in Figure 15-4.
这个字符串值作为构造器参数被传递给MvcHtmlString对象,并被注入到发送给客户端的响应。你可以在图15-4看到这一修改的效果。

图15-4

Figure 15-4. Bypassing encoding of HTML strings
图15-4. 绕过HTML字符器的编码

You can see the problem that raw strings present. We have managed to insert a tiny HTML form into the web page.
你可以看出原始字符串所显示的问题。我们已经设法把一个小型的HTML表单插入到了这个web页面。

■ Caution You might think that a user would spot an addition to a web page such as the one in Figure 15-4. If this is the basis of your security policy, then trouble is heading your way. First, real XSS attacks can be very subtle and sophisticated, and typically change the behavior of the page without changing the appearance. Second, you cannot rely on the attentiveness of users to protect your application from XSS attacks. Security is our job and not the responsibility of our users, however much we might like it to be otherwise.
注意:你也许会认为,用户会认出如图15-4所示的这种附加到web页面上的附加物。如果这是你的安全性策略思想,那么麻烦会一直伴随着你。首先,真正的XSS攻击可能是非常狡猾而老练的,典型的是可以修改页面的行为,而不改变其外观。其次,你不能依赖于用户的这种防止应用程序免于XSS攻击的意识。安全性是我们的工作而不是用户的职责,然而我们大多数人或许希望它是另一种方式。

It is not always possible to change the classes that generate your data, but you can disable encoding by using the @Html.Raw helper in a view, as shown in Listing 15-17.
并不总是有可能修改生成你数据的类,但你可以通过在视图使用@Html.Raw辅助器来取消编码,如清单15-17所示。

Listing 15-17. Bypassing HTML Encoding in a Razor View
清单15-17. 在一个Razor视图中取消HTML编码

@{ ViewBag.Title = "Index"; } Here is some data: @Html.Raw(Model) 

Using Dynamically Typed Views
使用动态类型视图

When you create a strongly typed view, the generated class that is produced from the view is derived from System.Web.Mvc.WebViewPage<T>, where T is the type of the view model class. For example, this view:
当你生成一个强类型视图时,产生于这个视图的生成类派生于System.Web.Mvc.WebViewPage<T>,这里,T是视图模型类的类型。例如,这个视图:

@model string @{ ViewBag.Title = "Index"; } ... rest of presentation logic and markup 

results in a compiled class that is defined like this:
会产生如下定义的编译类:

public class _Page_Views_Home_Index_cshtml : System.Web.Mvc.WebViewPage<string> { ... rest of class } 

The @model type of string is used as the type parameter for the generated class. As we explained earlier, this allows you to use the members of the view model class without needing to cast the object, which is neat and convenient, and makes unit testing simpler.
@model的类型string被用作为生成类的类型参数。正如我们前面解释的那样,这允许你使用视图模型类的成员,而不必进行对象转换,这是简洁而方便的,并且更易于单元测试。

If you don’t specify the type of the model, you create a dynamically typed view. For example, this view doesn’t specify a view model type:
如果你不指定这个模型的类型,便生成一个动态类型视图。例如,以下视图未指定视图模型类型:

@{ ViewBag.Title = "Index"; } ... rest of presentation logic and markup 

The class that is generated from this view is defined like this:
这个视图生成的类是这样定义的:

public class _Page_Views_Home_InlineHelper_cshtml : System.Web.Mvc.WebViewPage<dynamic> { ... rest of class } 

Notice that the generic type parameter for the base class is dynamic. You can still pass view model objects as you would for strongly typed views, but they are presented to the view as dynamic objects, meaning that dealing with the view model object becomes much like dealing with the ViewBag.
注意,基类的泛型类型参数是动态的。你仍然可以像强类型视图那样传递视图模型对象,但它们是作为动态对象表现给视图的,意即,处理这个视图模型对象变得很像处理ViewBag一样。

You can call any member you like on the view model object, but it won’t be evaluated until runtime. The positive aspect of this is that you can pass different view model types from the controller to the view. The downside is that unexpected runtime errors will occur if you pass an object that doesn’t have the members that you call in the view.
你可以调用这个视图模型对象上的任何成员,但直到运行时才会求取这个成员的值。这种做法积极的方面是你可以通过控制器把不同的视图模型传递给视图。不利的方面是,如果你传递的对象没有你要在视图中调用的成员,将会出现意想不到的运行时错误。

Listing 15-18 shows a view that is dynamically typed. Programmers who use this feature tend to want to work with the flexibility of a dynamic languages such as Ruby. They know the types of the values they are passing from the controller to the view.
清单15-18演示了一个动态类型视图。使用这种特性的程序员倾向于希望工作具有如Ruby那样的动态语言灵活性。他们清楚地知道控制器传递给视图的值的类型。

Listing 15-18. A Dynamically Typed Razor View
清单15-18. 一个动态类型的Razor视图

@{ ViewBag.Title = "Index"; } Dynamic view model: @Model[0] Model contents are: @Model 

■ Caution We don’t recommend you use this feature casually. If you can exert any control over the objects that your action methods produce, then you should use a strongly typed view instead.
注意:我们不建议你随意地使用这种特性。如果你能够尽力控制你的动作方法所产生的对象,那么,你应该使用强类型视图。

Using HTML Helpers
使用HTML辅助器

A common need is to generate the same block of HTML repeatedly in a view. Duplicating the same HTML elements and Razor tags over and over again is tedious and prone to errors. Furthermore, if you need to make changes, you must do so in multiple places. Fortunately, the MVC Framework provides a mechanism for solving the problem, called HTML helpers. In this section, we’ll show you how to create the two different kinds of HTML helpers that are available and demonstrate the built-in helpers that are included with the MVC Framework.
一个常见的需求是在视图中反复地生成一些同样的HTML块。一次又一次地重复同样的HTML元素和Razor标签是乏味而易错的。进一步地,如果你需要进行修改,你必须在多个地方做同样的事情。幸运的是,MVC框架提供了解决这一问题的机制,即HTML辅助器(注:本章所说的“辅助器”或“辅助器方法”与前面几章的“辅助方法”是一致的 — 译者注)。在本小节中,我们将向你演示如何生成两种不同的有用的HTML辅助器,并演示MVC框架引入的一些内建的辅助器。

Creating an Inline HTML Helper
生成内嵌HTML辅助器

The most direct way to create a helper is to do so in the view, using the Razor @helper tag. Listing 15-19 contains an example.
生成一个辅助器最直接的方式是在视图做这件事,即使用Razor的@helper标签。清单16-19包含了一个例子。

Listing 15-19. Creating an Inline Helper
清单15-19. 生成一个内嵌辅助器

@{ ViewBag.Title = "InlineHelper"; } @helper CreateList(string[] items) { <ul> @foreach (string item in items) { <li>@item</li> } </ul> } <h4>InlineHelper</h4> Days of the week: <p/> @CreateList(ViewBag.Days) <p /> Fruit I like: <p /> @CreateList(ViewBag.Fruits) 

Inline helpers have names and parameters similar to regular C# methods. In the example, we defined a helper called CreateList, which takes a string array as a parameter.
类似于规则的C#方法,内嵌辅助器具有名字和参数。在这个例子中,我们定义了一个名为CreateList的辅助器,它以一个字符串数组为参数。

The body of an inline helper follows the same syntax as the rest of a Razor view. Literal strings are regarded as static HTML, and statements that require processing by Razor are prefixed with the @character. The helper in the example mixes static HTML and Razor tags to create an unnumbered list from a string array. The output of this view is shown in Figure 15-5.
内嵌辅助器的体遵循Razor视图其余部分的同样语法。文字字符串被视为静态的HTML,而需要由Raozr处理的语句以@字符为前缀。这个例子中的辅助器混用了静态HTML和Razor标签,从一个字符串数组来生成一个无序列表。该视图的输出如图15-5所示。

图15-5

Figure 15-5. Using an inline helper
图15-5. 使用一个内嵌辅助器

We called the helper twice in the view to create a pair of lists. Without the helper, we would have needed to duplicate the HTML and the foreach loop that each list required.
在这个视图中,我们两次调用了这个辅助器,以生成两个列表。没有这个辅助器,我们在每个列表中都需要重复其HTML和foreach循环。

Although an inline helper looks like a method, there is no return value. The contents of the helper body are processed and put into the response to the client.
虽然内嵌辅助看上去像一个方法,但它没有返回值。辅助器体的内容被处理,并被放到了客户端响应之中。

Creating an External Helper Method
生成外部辅助器方法

Inline helpers are convenient, but they can be used only from the view in which they are declared. If they involve too much code, they can take over that view and make it hard to read. The alternative is to create an external HTML helper method, which is expressed as a C# extension method. Listing 15-20 provides a demonstration.
内嵌辅助器是方便的,但它们只能在它们声明的视图之中使用。如果它们包括了太多的代码,可能会占据视图并难以阅读。一个可选的办法是生成一个外部的HTML辅助器方法,它被表示成一个C#扩展方法。清单15-20提供了一个演示。

Listing 15-20. Creating an External HTML Helper
清单15-20. 生成一个外部HTML辅助器

using System.Web.Mvc; namespace DynamicData.Infrastructure.HtmlHelpers { public static class CustomHtmlHelpers { public static MvcHtmlString List(this HtmlHelper html, string[] listItems) { TagBuilder tag = new TagBuilder("ul"); foreach (string item in listItems) { TagBuilder itemTag = new TagBuilder("li"); itemTag.SetInnerText(item); tag.InnerHtml += itemTag.ToString(); } return new MvcHtmlString(tag.ToString()); } } } 

The first parameter to an external HTML helper method is an HtmlHelper object, annotated with the this keyword (this is what tells the C# compiler that we are defining an extension method). Any other parameters you define will allow you to pass values from the view to the helper. In our case, we are re- creating our inline helper, so we must define a string array parameter that will contain the list items to be rendered.
送给外部HTML辅助器方法的第一个参数是一个HtmlHelper对象,它以this关键词进行注释(this是告诉C#编译器,我们正在定义的是一个扩展方法)。你所定义的任何其它参数允许你把视图的值传递给这个辅助器。在我们的例子中,我们重新生成了前面的内嵌辅助器,因此,我们必须定义一个字符串数组参数,它含有用来呈现的列表项。

The easiest way to create HTML in a helper method is to use the TagBuilder class, which allows you to build up HTML strings without needing to deal with all of the escaping and special characters. The TagBuilder class is part of the System.Web.WebPages assembly, but uses a feature called type forwarding to appear as though it is part of the System.Web.Mvc assembly. Both assemblies are added to MVC projects by Visual Studio, so you can use the TagBuilder class easily enough, but it doesn’t appear in the Microsoft Developer Network (MSDN) API documentation.
在一个辅助器方法中,生成HTML最容易的方式是使用TagBuilder类,它允许你建立HTML字符串,而不需要处理各种转义及特殊字符之类的事情。TabBuilder类属于System.Web.WebPages程序集,但使用了一个叫做类型转发的特性,使它看上去好像属于System.Web.Mvc程序集一样。这两个程序集都由Visual Studio加到了MVC的项目,因此你可以很容易地使用TagBuilder类,但它并未出现在微软开发者网络(MSDN)的API文档中。

We create a new TagBuilder instance, passing in the HTML element we want to construct as the constructor parameter. We don’t need to use the angle brackets (< and >) with the TagBuilder class, which means we can create a ul element, like this:
我们生成了一个新的TagBuilder实例,把我们想要构造的HTML元素传递给它作为其构造器参数。我们不需要使用角括号(<和>),意即我们可以像下面这样来生成ul元素:

TagBuilder tag = new TagBuilder("ul"); 

The most useful members of the TagBuilder class are described in Table 15-3.
TagBuilder类最有用的成员如表15-3所示。

Table 15-3. Some Members of the TagBuilder Class
表15-3. TabBuilder类的一些成员
Member
成员
Description
描述
InnerHtmlA property that lets you set the contents of the element as an HTML string. The value assigned to this property will not be encoded, which means that is can be used to nest HTML elements.
让你把元素内容设置为HTML字符串的一个属性。赋给这个属性的值将不进行编码,意即,可以把它嵌入HTML元素。
SetInnerText(string)Sets the text contents of the HTML element. The string parameter is encoded to make it safe to display (see the section on HTML string encoding earlier in the chapter for details).
设置HTML元素的文本内容。字符串参数将被编码,以使它安全显示(详见本章前面的HTML字符串编码小节)。
AddCssClass(string)Adds a CSS class to the HTML element.
把一个CSS的class添加到这个元素。
MergeAttribute(string, string, bool)Adds an attribute to the HTML element. The first parameter is the name of the attribute, and the second is the value. The bool parameter specifies if an existing attribute of the same name should be replaced.
把一个性质添加到这个HTML元素。第一个参数是性质名,第二个是它的值。bool参数指定是否替换已存在的同名性质。

The result of an HTML helper method is an MvcHtmlString object, the contents of which are written directly into the response to the client. Remember that the contents of an MvcHtmlString will be passed directly into the response without being encoded, which means that you must take care to ensure that you encode any data that may present an XSS risk. You can do that by using the TagBuilder.SetInnerText method, which will encode a string for you, or do so explicitly by using the Encode method of the HtmlHelper object that you receive as a parameter to your helper method.
HTML辅助器方法的结果是一个MvcHtmlString对象,其内容被直接写入客户端响应。记住,MvcHtmlString的内容将被直接传递给响应,而不进行编码,这意味着,你必须小心地确保对可能出现XSS攻击的数据进行编码。你可以通过TagBuilder.SetInnerText方法来做这件事,它将为你对一个字符串进行编码,或者,用HtmlHelper对象的Encode方法来明确地做这件事。

■ Note In order to use a custom external HTML helper method, you must import the namespace that contains the class. See the “Importing Namespaces into a View” section earlier in the chapter for details on how to do this.
注:为了使用一个自定义的外部HTML辅助器方法,你必须引入包含这个类的命名空间。参见本章前面如何这件事的“将命名空间引入视图”小节。

Using the Built-in HTML Helpers
使用内建的HTML辅助器

The MVC Framework includes a selection of built-in HTML helper methods that generate often-required HTML fragments or perform common tasks. We’ve used some of these helpers in previous chapters. In the following sections, we’ll put them in context and introduce some new ones.
MVC框架包含了一些生成常用HTML片段、或执行常规任务的内建HTML辅助器方法。这些辅助器方法我们前面几章已经使用过一些。在以下小节中,我们将把它们放到一定的上下文情景中,并介绍一些新的辅助器。

■ Note Using the HTML helpers to generate HTML elements like forms and inputs is not compulsory. If you prefer, you can code them using static HTML tags and populate values using view data or view model objects. The HTML that the helpers generate is very clean, and there are no special meanings to the attribute values. The helpers are there for convenience, rather than because they create essential or special HTML.
注:使用HTML辅助器来生成一些诸如表单(<form>)、输入(<input>)之类的HTML元素并不是必须的。如果你喜欢,你可以用静态HTML标签,并注入视图数据或视图模型对象对它们进行编码。这些辅助器方法生成的HTML是非常干净的,而且不会把特定含义指定给性质的值。辅助器方法是为了方便,而不是因为它们生成基本的或特殊的HTML。

Creating Forms
生成表单

Two of the most useful (and most commonly used) helpers are Html.BeginForm and Html.EndForm. These helpers create HTML form tags and generate a valid action attribute for the form that is based on the routing mechanism for the application. Listing 15-21 shows the use of these helpers.
两个最有用(而且最常用)的辅助器是Html.BeginForm和Html.EndForm。这些辅助器生成HTML的form标签,并且基于应用程序的路由机制为这个form生成一个有效的action性质。清单15-21演示了这些辅助器的使用。

Listing 15-21. Using the BeginForm and EndForm Helpers
清单15-21. 使用BeginForm和EndForm辅助器

@{Html.BeginForm("Index", "Home");} @{ Html.EndForm();} 

Neither of these helpers returns a value that can be injected into to the output directly, so you need to invoke them inside a Razor code block, which makes for an ugly syntax. A more elegant approach is to employ a using statement, as shown in Listing 15-22.
这两个辅助器都不会返回直接注入到输出的值,因此,你需要在一个Razor代码块中调用它们,这形成了一个丑陋的语法。一个更雅致的办法是借助一个using语句,如清单15-22所示。

Listing 15-22. Creating HTML with BeginForm and a using Statement
清单15-22. 用BeginForm和一个using语句生成HTML

@using (Html.BeginForm("Index", "Home")) { } 

This is a neat trick that works because the BeginForm method creates an instance of the MvcForm class, which implements the IDisposable interface. When you exit the using block, the .NET Framework calls the Dispose method on the MvcForm object, which causes it to emit the closing form tag and spares you the need to use the EndForm helper.
这是从事工作的一个优雅的技巧,因为BeginForm方法生成一个MvcForm类的实例,它实现了IDisposable接口。当你退出using块时,.NET框架会调用MvcForm对象上的Dispose方法,这使它可以忽略form的关闭标签,并省去了使用EndForm辅助器的需要。

The HTML generated by both Listings 15-21 and 15-22 is the same:
由清单15-21和15-22生成的HTML是相同的:

<form action="/" method="post"></form> 

The BeginForm helper is overloaded, and the different forms allow you to specify how the action attribute of the HTML form element is created. The version we used in Listings 15-21 and 15-22 allows us to specify an action method and a controller. These details are used in conjunction with the routing system to generate an action attribute value. In this case, the Index action on the Home controller is the default for the application, and so the action URL is set to /. See Chapter 11 for details on the routing system and how it can be used to create URLs.
BeginForm辅助器是过载的,而且不同的形式允许你指定如何生成这个form元素的action性质。我们在清单15-21和15-22中所使用的这个版本,允许我们指定一个动作方法和一个控制器。这些细节被用于协助路由系统生成action性质的值。在这个例子中,Home控制器的Index动作是这个应用程序的默认值,因此,动作的URL被设置为/。详见第11章关于路由系统以及如何用它来生成URL。

CREATING FORMS THAT POSTBACK TO THE SAME URL
生成回发到相同URL的表单

The default overload of the BeginForm helper takes no parameters and generates a URL that refers back to the current action—that is, the one that processed the current request. This is a common pattern in MVC applications, where the same URL is used to handle GET and POST requests. Typically, a GET request displays the HTML form, and the POST request validates and processed the submitted data. To make this work most effectively, create two action methods with the same name, like this:
BeginForm辅助器的默认过载不带参数,并生成一个回指向当前动作的URL — 即,处理当前请求的URL。这是MVC应用程序的常规模式,用同样的URL来处理GET和POST请求。典型地,一个GET请求显示这个HTML表单,而POST请求校验并处理递交的数据。为了最有效地执行这项工作,最好生成两个同名的动作方法,像这样:

public class SomeController : Controller { public ViewResult MyAction() { /* Displays the form */ } [HttpPost] public ActionResult MyAction(MyModel incomingData) { /* Handles the POST */ } } 

The HttpPost attribute is an action method selector, which we explained in Chapter 14. The process the MVC Framework uses to take the data items from the submitted form and generate the MyModel object (or our project equivalent) is explained in Chapter 17.
这个HttpPost性质是我们在第14章解释过的一个动作方法选择器。MVC框架用来获取递交表单的数据项、以及MyModel对象(或与项目相关的对象)的过程将在第17章解释。

Using Input Helpers
使用input辅助器

An HTML form is of no use unless you also create some input elements. Table 15-4 shows the basic HTML helpers that are available to create input elements. Each of these helpers is overloaded. The table shows the simplest version. The first parameter is used to set attribute values in the generated HTML, such as id and name. The second parameter is used to set the value attribute of the input element (or the inner content for the TextArea).
光有一个HTML的form是没用的,除非你还生成了一些input元素。表15-4显示了可用于生成input元素的一些基本的HTML辅助器。每个辅助器都是过载的。该表显示的是最简单的版本。第一个参数用于设置所生成的HTML元素的性质值,如id、name等。第二个参数用于设置input元素的value性质(或TextArea内部的内容)。

Table 15-4. Basic Input HTML Helpers
表15-4. 基本的input辅助器
HTML Element
HTML元素
Example
例子
Checkbox
检查框
Html.CheckBox("myCheckbox", false)
Output(输出): 
<input id="myCheckbox" name="myCheckbox" type="checkbox" value="true" /> 
<input name="myCheckbox" type="hidden" value="false" />
Hidden field
隐藏字段
Html.Hidden("myHidden", "val")
Output(输出): 
<input id="myHidden" name="myHidden" type="hidden" value="val" />
Radio button
无线按钮
Html.RadioButton("myRadiobutton", "val", true) 
Output(输出): 
<input checked="checked" id="myRadiobutton" name="myRadiobutton"
type="radio" value="val" />
Password
口令字段
Html.Password("myPassword", "val")
Output(输出): 
<input id="myPassword" name="myPassword" type="password" value="val" />
Text area
文本区
Html.TextArea("myTextarea", "val", 5, 20, null) 
Output(输出): 
<textarea cols="20" id="myTextarea" name="myTextarea" rows="5"> 
val</textarea>
Text box
文本框
Html.TextBox("myTextbox", "val")
Output(输出): 
<input id="myTextbox" name="myTextbox" type="text" value="val" />

■ Note Notice that the checkbox helper (Html.CheckBox) renders two input elements. It renders a checkbox and then a hidden input element of the same name. This is because browsers don’t submit a value for checkboxes when they are not selected. Having the hidden control ensures that the MVC Framework will get a value from the hidden field when this happens.
注:注意,checkbox辅助器(Html.CheckBox)呈现两个input元素。它呈现一个检查框(checkbox)和一个随后隐藏的同名input元素。这是因为浏览器在检查框未作出选择时,不会递交检查框的值。有了这个隐藏的控件,可以确保MVC框架在作出选择后从这个隐藏字段获得一个值。

If you want to specify a value, you can do so by using the standard Razor @ syntax, like this:
如果你想指定一个值,你可以通过标准的Razor的@语法来做这件事,像这样:

@Html.CheckBox("myCheckBox", Model) 

A more interesting overload takes a single string argument, which is then used to search the view data, ViewBag, and view model. So, for example, if you call @Html.TextBox("DataValue"), the MVC Framework tries to find some item of data that corresponds with the key DataValue. The following locations are checked:
一个更有趣的过载以一个单一的字符串为参数,它然后被用于搜索ViewBag的视图数据和视图模型。因此,如果你调用@Html.TextBox("DataValue"),MVC框架将查找与这个键DataValue相关联的一些数据值。这将检查以下位置:

  • ViewBag.DataValue
  • ViewData["DataValue"]
  • @Model.DataValue

The first value that is found is used to set the value attribute of the generated HTML. (The last check, for @Model.DataValue, works only if the view model for the view contains a property or field called DataValue.)
找到的第一个值被用来设置所生成的HTML的性质的值。(最后检查的位置,@Model.DataValue,只有视图的视图模型含有一个叫做DataValue的属性或字段时,才会工作。)

If we specify a string like DataValue.First.Name, the search becomes more complicated. The MVC Framework will try different arrangements of the dot-separated elements, such as the following:
如果我们指定一个如DataValue.First.Name这样的字符串,这种搜索就会变得更复杂。MVC框架会试行这个点分隔元素的不同排列,比如像这样:

  • ViewBag.DataValue.First.Name
  • ViewBag.DataValue["First"].Name
  • ViewBag.DataValue["First.Name"]
  • ViewBag.DataValue["First"]["Name"]
  • ViewData["DataValue.First.Name"]
  • ViewData["DataValue"].First.Name
  • ViewData["DataValue.First"].Name

Many permutations will be checked. Once again, the first value that is found will be used, terminating the search. There is an obvious performance consideration to this technique, but bear in mind that usually only a few items are in the ViewBag and ViewData, so it doesn’t take much time to search through them.
这会检查各种变换。再一次地,找到的第一个值就会被运用,搜索随之终止。这一技术显然有一个性能问题,但请记住,在ViewBag及ViewData中通常只有几个数据项,因此,搜遍这些数据项不需要太多的时间。

■ Note The input helpers automatically encode data values to make them safe to display. See the “Understanding Razor HTML String Encoding” section earlier in this chapter for details of why this is important.
注:input辅助器自动地对数据值编码,以使它们安全显示。参见本章前面的“理解Razor的HTML字符串编码”小节,以详细了解为什么这很重要。

Using Strongly Typed Input Helpers
使用强类型input辅助器

For each of the basic input helpers that we described in Table 15-4, there are corresponding strongly typed helpers, which we can use in strongly typed views. Table 15-5 shows the strongly typed input helpers and samples of the HTML they generate. These helpers can be used only with strongly typed views.
对于表15-4所描述的每个基本的input辅助器,都有一个对应的强类型input辅助器,我们可以把它们用于强类型视图。表15-5显示了强类型input辅助器及其生成HTML的例子。这些辅助器只可以用于强类型视图。

Table 15-5. Strongly Typed Input HTML Helpers
表15-5. 强类型input辅助器
HTML Element
HTML元素
Example
例子
Checkbox
检查框
Html.CheckBoxFor(x => x.IsApproved)
Output(输出): 
<input id="IsApproved" name="IsApproved" type="checkbox" value="true" /> 
<input name="IsApproved" type="hidden" value="false" />
Hidden field
隐藏字段
Html.HiddenFor(x => x.SomeProperty) 
Output(输出): 
<input id="SomeProperty" name="SomeProperty" type="hidden" value="value" />
Radio button
无线按钮
Html.RadioButtonFor(x => x.IsApproved, "val")
Output(输出): 
<input id="IsApproved" name="IsApproved" type="radio" value="val" />
Password
口令字段
Html.PasswordFor(x => x.Password) 
Output(输出): 
<input id="Password" name="Password" type="password" />
Text area
文本区
Html.TextAreaFor(x => x.Bio, 5, 20, new{})
Output(输出): 
<textarea cols="20" id="Bio" name="Bio" rows="5"> 
Bio value</textarea>
Text box
文本框
Html.TextBoxFor(x => x.Name) 
Output(输出): 
<input id="Name" name="Name" type="text" value="Name value" />

The strongly typed input helpers work on lambda expressions. The value that is passed to the expression is the view model object, and you can select the field or property that will be used to set the value attribute.
这些强类型input辅助器工作于lambda表达式。传递给表达式的值是视图模型对象、以及你可以选择的字段或属性,它们将被用于设置value性质。

ADDING ATTRIBUTES TO HTML
添加HTML性质

Most of the HTML helper methods have an overloaded version that lets you add arbitrary attributes to the generated HTML. You can express the attributes and the associated values using a dynamic type, like this:
大多数HTML辅助器方法都有一个过载版本,让你对生成的HTML添加任意性质。你可以用一个动态类型来表示这个性质及其关联的值,像这样:

@Html.TextBox("MyTextBox", "MyValue", new { @class = "my-ccs-class", mycustomattribute = "my-value" }) 

This creates a text box element that has the additional attributes class and mycustom attribute, like this:
它生成一个文本框元素,该元素有额外的性质class和mycustom,像这样:

<input class="my-ccs-class" id="MyTextBox" mycustomattribute="my-value" name="MyTextBox" type="text" value="MyValue" /> 

One of the most common attributes to add this way is class, in order to assign CSS styles to elements. You must prefix class with an @ character to stop the C# compiler from assuming that you are defining a new type. This use of the @ character is unrelated to Razor. It is a standard C# feature that permits the use of reserved words as variables.
以这种方式添加的最常用的性质是class,以便把CSS样式分配给元素。你必须在class的前面缀以@字符,以阻止C#编译器认为你是在定义一个新类型。@字符的使用与Razor无关。它是允许把保留字用作为变量的一个标准的C#特性。

Creating Select Elements
生成select元素

Table 15-6 shows the HTML helpers that can be used to create select elements. These can be used to select a single item from a drop-down list or present a multiple-item select element that allows several items to be selected. As with the other form elements, there are versions of these helpers that are weakly and strongly typed.
表15-6显示了可以用来生成select元素的HTML辅助器。这些可以用于从一个下拉列表选择一个单项,或表现一个允许多项选择的多项select元素。

Table 15-6. HTML Helpers That Render Select Elements
表15-6. 呈现select元素的HTML辅助器
HTML Element
HTML元素
Example
例子
Drop-down list
下拉列表
Html.DropDownList("myList", new SelectList(new [] {"A", "B"}), "Choose")
Output(输出): 
<select id="myList" name="myList"> 
<option value="">Choose</option> 
<option>A</option> 
<option>B</option> 
</select>
Drop-down list
下拉列表
Html.DropDownListFor(x => x.Gender, new SelectList(new [] {"M", "F"}))
Output(输出): 
<select id="Gender" name="Gender"> 
<option>M</option> 
<option>F</option> 
</select>
Multiple-select
多项选择
Html.ListBox("myList", new MultiSelectList(new [] {"A", "B"}))
Output(输出): 
<select id="myList" multiple="multiple" name="myList"> 
<option>A</option> 
<option>B</option> 
</select>
Multiple-select
多项选择
Html.ListBoxFor(x => x.Vals, new MultiSelectList(new [] {"A", "B"}))
Output(输出): 
<select id="Vals" multiple="multiple" name="Vals"> 
<option>A</option> 
<option>B</option> 
</select>

The select helpers take SelectList or MultiSelectList parameters. The difference between these classes is that MultiSelectList has constructor options that let you specify that more than one item should be selected when the page is rendered initially.
select辅助器以SelectList或MultiSelectList为参数。这些类之间的差异是MultiSelectList有构造器选项,在最初呈现页面时,让你指定被选择的多个初值。

Both of these classes operate on IEnumerable sequences of objects. In Table 15-6, we created arrays that contained the list items we wanted displayed. A nice feature of SelectList and MultiSelectList is that they will extract values from objects for the list items. For example, suppose that we have a class called Region, as follows:
这两个类在IEnumerable对象序列上进行操作。在表15-6中,我们生成了含有我们希望显示的列表项。SelectList和MultiSelectList的一个很好的特性是它们将为这个列表项提取对象的值。例如,假设我们有一个名为Region的类,如下所示:

public class Region{ public int RegionID { get; set; } public string RegionName { get; set; } } 

Now suppose our action method puts a SelectList object into the view data like this:
现在假设我们的动作把一个SelectList对象放入视图,像这样:

List<Region> regionsData = new List<Region> { new Region { RegionID = 7, RegionName = "Northern" }, new Region { RegionID = 3, RegionName = "Central" }, new Region { RegionID = 5, RegionName = "Southern" }, }; ViewData["region"] = new SelectList(regionsData, // items "RegionID",// dataValueField "RegionName", // dataTextField 3);// selectedValue 

In this case, <%: Html.DropDownList("region", "Choose") %> will render the following HTML:
在这个例子中,<%: Html.DropDownList("region", "Choose") %>将呈现以下HTML:

<select id="region" name="region"> <option value="">Choose</option> <option value="7">Northern</option> <option selected="selected" value="3">Central</option> <option value="5">Southern</option> </select> 

As you can see, the values of the RegionID and RegionName properties have been used for the inner text and the value attributes of the option items contained within the select element.
正如你所看到的,RegionID和RegionName属性的值已经被用作为这个select元素中可选项的内部文本和value性质。

Creating Links and URLs
生成连接和URL

The next set of HTML helpers allows you to render HTML links and raw URLs using the routing system’s outbound URL-generation capabilities, which we discussed in Chapter 11. Table 15-7 describes the available HTML helpers. The output from these helpers is dependent on the routing configuration of the MVC application.
另一组HTML辅助器允许你运用路由系统的出站URL生成能力来呈现HTML连接和原始URL,路由系统的出站URL生成能力我们在第11章讨论过。表15-17描述了这些有用的HTML辅助器。这些辅助器的输出依赖于MVC应用程序的路由配置。

Table 15-7. HTML Helpers That Render URLs
表15-7. 呈现URL的HTML辅助器
Description
描述
Example
例子
Application-relative URL
应用程序相对URL
Url.Content("~/my/content.pdf")
Output(输出): 
/my/content.pdf
Link to named action/controller
连接到指定动作/控制器
Html.ActionLink("Hi", "About", "Home")
Output(输出): 
<a href="http://archive.cnblogs.com/Home/About" target="_blank" rel="nofollow">
Link to absolute URL
连接到绝对URL
Html.ActionLink("Hi", "About", "Home", "https","www.example.com", "anchor", new{}, null) 
Output(输出): 
<a href="https://www.example.com/Home/About#anchor" target="_blank" rel="nofollow">
Raw URL for action
用于动作的原始URL
Url.Action("About", "Home")
Output(输出): 
/Home/About
Raw URL for route data
用于路由数据的原始URL
Url.RouteUrl(new { controller = "c", action = "a" })
Output(输出): 
/c/a
Link to arbitrary route data
连接到任意路由数据
Html.RouteLink("Hi", new { controller = "c", action = "a" }, null) 
Output(输出): 
<a href="http://archive.cnblogs.com/c/a" target="_blank" rel="nofollow">
Link to named route
连接到指定路由
Html.RouteLink("Hi", "myNamedRoute", new {})
Output(输出): 
<a href="http://archive.cnblogs.com/url/for/named/route" target="_blank" rel="nofollow">

■ Tip In every case except Url.Content, you can supply a collection of additional routing parameters to aid the routing system in generating the URL. These parameters are expressed as properties and values in an anonymous type. See Chapter 11 for further details and examples.
提示:在除了Url.Content的每个例子中,你可以把一个额外的路由参数提供给生成这个URL的目标路由系统。这些参数以一个匿名类型表示成属性和值。参见第11章的进一步细节和示例。

Using the WebGrid Helper
使用WebGrid辅助器

The WebGrid helper generates HTML to display data items in a grid format, using the HTML table element. This helper is a wrapper around a ASP.NET Web Pages control. It is a nice bonus to be able to use it with the MVC Framework but, as you will see, it is a little awkward.
WebGrid辅助器生成以网格形式显示数据项的HTML,它使用HTML的table元素。这个辅助器是ASP.NET Web页面控件的一个包装程序。这是能够随MVC框一起使用的一个很好的工具,但正如你马上看到的,它也有一点笨拙。

Describing the WebGrid helper makes it seem more complex than it really is, just because there are so many configuration options. To start with, you need a source of items to display. We have created an action method that returns a collection of Product objects from the SportsStore application we built earlier in this book. Listing 15-23 shows the action method.
描述WebGrid辅助器似乎比它的实际情况更复杂些,这只是因为有很多配置选项。作为开始,你需要一个要显示的数据项源。在本书前面,我们建立了SportsStore应用程序,生成了返回Product对象集合的一个动作方法。

Listing 15-23. Creating a Sequence of Objects to Be Displayed by the WebGrid Helper
清单15-23. 生成由WebGrid辅助器显示的对象序列

public ActionResult Grid() { IEnumerable<Product> productList = new List<Product> { new Product {Name = "Kayak", Category = "Watersports", Price = 275m}, new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95m}, new Product {Name = "Soccer ball", Category = "Football", Price = 19.50m}, new Product {Name = "Corner flags", Category = "Football", Price = 34.95m}, new Product {Name = "Stadium", Category = "Football", Price = 79500m}, new Product {Name = "Thinking cap", Category = "Chess", Price = 16m} }; return View(productList); } 

The action method passes an IEnumerable<Product> as the view model object. Listing 15-24 shows how we can display this data using the WebGrid helper.
该动作方法传递一个IEnumerable<Product>作为视图模型对象。清单15-24演示了我们如何用WebGrid辅助器显示这个数据。

Listing 15-24. Using the WebGrid Helper
清单15-24. 使用WebGrid辅助器

@model IEnumerable<DynamicData.Models.Product> @{ var grid = new WebGrid( source: Model, rowsPerPage: 4); } @grid.GetHtml( tableStyle: "grid", headerStyle: "header", rowStyle: "row", footerStyle: "footer", alternatingRowStyle: "altRow", columns: grid.Columns ( grid.Column("Name", "Item", style:"textCol"), grid.Column("Price", style: "numberCol", format: @<text>$@string.Format("{0:F2}", item.Price) </text>) )) 

We use the WebGrid helper in two parts. The first part is contained in a Razor code block and defines the WebGrid object. In our example, we set the source parameter to be the view model (the sequence of Product objects). These are the items that will be displayed as the rows in the grid. We have split the definition and use of the grid into two stages (you’ll see why this is useful shortly). The other parameter we have provided a value for is rowsPerPage. The WebGrid supports pagination through a multipage table.
我们以两个部分来使用这个WebGrid辅助器。第一部分包含在一个Razor代码块中,并定义这个WebGrid对象。在我们的例子中,我们把source参数设置为视图模型(Product对象的序列)。这些是在网格中作为一行显示的条目。我们已经把网格的定义和使用分隔成两个阶段(你很快就明白这是有用的)。我们提供了一个值的另一个参数是rowsPerPage。

There are quite a few constructor parameters that you can specify to define the behavior of the grid that is generated. Some of the most useful are described in Table 15-8.
有不少你可以指定的构造器参数来定义所生成的网格的行为。一些最有用的如表15-8所示。

Table 15-8. Some Constructor Parameters for the WebGrid Helper Class
表15-8. WebGrid辅助器类的构造器参数
Parameter
参数
Default Value
默认值
Description
描述
SourcenullSets the items to be displayed in the grid
设置在网格中显示的数据项
columnNamesnullDefines the parameters from the data source items that will be used for columns
定义数据源项的参数,它们将被用于网格的列
rowsPerPage10Specifies how many rows should be displayed on each page of the grid
指定网格的每一页显示多少行
canPagetrueEnables or disables paging
使用或取消分页
canSorttrueEnables or disables sorting the data when the user clicks a column
使用或取消用户点击一列时对数据排序

The second part of using the WebGrid helper is to generate the HTML, which we do by calling the GetHtml method on the WebGrid object we created previously. The parameters that we pass to this method let us control the way that the HTML is rendered, as opposed to the constructor parameters, which let us set up the overall configuration.
使用WebGrid辅助器的第二部分是生成HTML,我们通过在前面生成的WebGrid对象上调用GetHtml方法来做这件事。我们传递给这个方法的参数让我们控制呈现HTML的方式,与构造器参数不同,这让我们建立整个配置。

Table 15-9 shows some of the most useful parameters that can be passed to the GetHtml method.
表15-9显示了能够传递给GetHtml方法的一些最有用的参数

Table 15-9. Some Parameters for the WebGrid.GetHtml Method
表15-9. WebGrid.GetHtml方法的一些参数
Parameter
参数
Default Value
默认值
Description
描述
tableStylenullSets the CSS class for the table element
为table元素设置CSS的class
headerStylenullSets the CSS class for the header tr element
为表头的tr元素设置CSS的class
footerStylenullSets the CSS class for the footer tr element
为表脚的tr元素设置CSS的class
rowStylenullSets the CSS class for table rows
为表行设置CSS的class
alternatingRowStylenullSets the CSS class for alternating table rows
为交替的表行设置CSS的class
captionnullSets the text for the caption element, displayed above the table
为显示在表上的caption元素设置文本
displayHeadernullEnables or disables the caption element
使用或取消caption元素
fillEmptyRowsfalseIf true, empty rows are added to the last page
如果为true,空行被添加到最后页面
previousText and
nextText
nullSet the text for the previous and next navigation links
设置向前和向后导航连接的文本
numericLinksCountnullSets the number of pages that will be displayed in the footer
设置在表脚中显示的页面数
columnsnullLets you configure individual columns (see the example after the table)
让你配置个别列(参见本表之后的示例)
htmlAttributesnullA dynamic type that specifies additional attributes for the table element (see the “Adding Attributes to HTML” sidebar for details)
这是一个动态类型,给table元素指定附加性质(详见“给HTML添加性质)

The columns parameter provides a means to configure individual columns in the table. You use the Columns method to create a collection of column configurations, each of which is created using the Column method:
columns参数提供了一种手段,以配置表的各个列。用Columns方法生成一个列配置的集合,其每一列都用Column方法生成:

... columns: grid.Columns ( grid.Column("Name", "Item", style:"textCol"), grid.Column("Price", style: "numberCol", format: @<text>$@string.Format("{0:F2}", item.Price) </text>) )) 

This example selects the Name property from the source objects for the first column and specifies that the column title should be Item and the CSS style should be textCol. The second column is the Price property, and we have formatted the column so that the values are shown as dollar currency amounts.
这个例子为第一列选择了源对象的Name属性,并指定列标题为Item,CSS样式为textCol。第二列是Price属性,而且对该列作了格式化,以使其值显示成美元货币量。

Table 15-10 describes the parameters you can pass to the Column method.
表15-10描述了你可以传递给Column方法的参数。

Table 15-10. Parameters for the WebGrid.Column Method
表15-10. WebGrid.Column方法的参数
Parameter
参数
Description
描述
CanSortEnables or disables sorting on this column
使用或取消本列的排序
ColumnNameThe property or field in the source object to display in this column
在本列显示的源对象的属性或字段
FormatSpecifies a Func that formats the data
指定一个格式化数据的Func
HeaderSets the header text for the column
设置列标题的文本
StyleSets the CSS style for the column
设置列的CSS样式

The output from the view in Listing 15-24 is shown in Figure 15-6. We have added some CSS styles to match the ones we assigned to the various elements in the GetHtml method.
清单15-24视图的输出如图15-6所示。我们已经添加了一些CSS样式,以匹配我们在GetHtml方法中赋给各种元素的样式。

图15-6

Figure 15-6. The result of using the WebGrid helper
图15-6. 使用WebGrid辅助器的结果

You can see the WebGrid helper’s party trick if you click one of the column headers or the navigation links in the table footer. The WebGrid automatically sets up row sorting and pagination through the data. For example, if you click the navigation link 2 in the footer, the page will reload and display the next set of items.
如果你点击一个列标题或表脚的导航连接,你可以看出WebGrid辅助器的主要功能。WebGrid根据数据自动地建立了行排序和分页。例如,如果你点击表脚的导航连接2,页面将重载并显示下一组数据项。

The WebGrid supports these features by invoking the action method again, but with some additional parameters. When you click the 2 link, the browser displays this URL:
WebGrid通过再次调用动作方法来支持这些特性,但带有一些附加参数。当你点击连接2时,浏览器显示这样的URL:

http://localhost:1223/Home/Grid?sortdir=ASC&page=2 

You can see that the URL includes query string values for sortdir and page. These are ignored by our action method, but detected and acted upon by the WebGrid object we created in the view.
你可以看出,这个URL包含了对sortdir和page的查询字串值。这些是被我们的动作方法忽略的,但会被我们在视图中生成的WebGrid对象所检测和执行。

■ Tip Bear in mind that the action method is invoked each time that the user clicks a column header or a navigation link. This means that the data to be displayed in the table is retrieved from the repository and all but the current page is discarded. If working with your repository is an expensive operation, then you should consider caching your data when using the WebGrid helper.
提示:要记住,每次用户点击列标题或导航连接时都调用这个动作方法。这意味着在表中显示的数据都从存储库接收而来,几乎全部丢弃当前页。如果与存储库进行工作是一种高负荷操作,那么,在使用WebGrid辅助器时,你应该考虑使用缓存。

We like the WebGrid helper, but we don’t like defining the WebGrid object in the view—it feels like something we should be doing in the action method. To that end, we tend to create the WebGrid and pass it to the view using the ViewBag, like this:
我们喜欢WebGri辅助器,但我们并不喜欢在视图中定义这个WebGrid对象 — 感觉这是我们应该在动作方法中所做的事情。说到底,我们倾向于(在动作方法中)生成这个WebGrid,并用ViewBag把它传递给视图,像这样:

public ActionResult Grid() { IEnumerable<Product> productList = new List<Product> { new Product {Name = "Kayak", Category = "Watersports", Price = 275m}, new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95m}, new Product {Name = "Soccer ball", Category = "Football", Price = 19.50m}, new Product {Name = "Corner flags", Category = "Football", Price = 34.95m}, new Product {Name = "Stadium", Category = "Football", Price = 79500m}, new Product {Name = "Thinking cap", Category = "Chess", Price = 16m} }; ViewBag.WebGrid = new WebGrid(source: productList, rowsPerPage: 4); return View(productList); } 

Then we retrieve it in the view, as follows:
然后我们在视图接收它,如下所示:

@model IEnumerable<DynamicData.Models.Product> @{ var grid = (WebGrid)ViewBag.WebGrid;} @grid.GetHtml( tableStyle: "grid", headerStyle: "header", rowStyle: "row", footerStyle: "footer", alternatingRowStyle: "altRow", columns: grid.Columns ( grid.Column("Name", "Item", style:"textCol"), grid.Column("Price", style: "numberCol", format: @<text>$@string.Format("{0:F2}", item.Price) </text>) )) 

We still define a local variable in the view, because we need to call methods on the WebGrid object when we set up the columns. There is no particular advantage of one approach over the other, but we like to keep our views as simple as possible.
我们在视图中仍然定义了一个局部变量,因为在建立表列时,我们需要在WebGrid对象上调用方法。与其它办法相比没有特别的优势,但我们喜欢尽可能保持我们的视图简单。

Using the Chart Helper
使用Chart辅助器

As its name suggests, the chart helper produces charts and graphs. This is another ASP.NET Web Forms control that you are able to employ in MVC Framework applications (and are ungainly to use as a consequence). Unlike the other helpers, the chart helper produces images rather than HTML.
如它的名字一样,chart辅助器产生图表和图形。这是另一个你可以在MVC框架应用程序中采用的ASP.NET的Web表单控件(而且它笨拙地用作为一种结果)。与其它辅助器不同,图表辅助器产生图像,而不是HTML(这是为什么说它被作为一种结果使用的原因 — 译者注)。

The chart helper has many, many options and can display a wide range of different chart types and styles—so many that we are going to give you only the briefest introduction in this book. The best way to learn more about the capabilities of this helper is by experimentation. The MSDN section on MVC is pretty thin on details, but the Web Forms coverage of the Chart control is more helpful. The simplest way to use the chart helper is to define an action method that returns void, as shown in Listing 15-25.
Chart辅助器有很多选项,并能够显示各种各样的图表类型和样式 — 本书中我们要给你的如此之多,以致我们只能简单介绍。了解这个辅助器更多能力的最好办法是实验。MSDN关于MVC的内容很不详尽,但Web表单涉及的Chart控制更有用。使用chart辅助器最简单的办法是定义一个返回void的动作方法,如清单15-25所示。

Listing 15-25. Using the Chart Helper
清单15-25. 使用Chart辅助器

public void ChartImage() { IEnumerable<Product> productList = new List<Product> { new Product {Name = "Kayak", Category = "Watersports", Price = 275m}, new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95m}, new Product {Name = "Soccer ball", Category = "Football", Price = 19.50m}, new Product {Name = "Corner flags", Category = "Football", Price = 34.95m}, new Product {Name = "Stadium", Category = "Football", Price = 150m}, new Product {Name = "Thinking cap", Category = "Chess", Price = 16m} }; Chart chart = new Chart(400, 200, @"<Chart BackColor=""Gray"" BackSecondaryColor=""WhiteSmoke"" BackGradientDiagonalRight"" AntiAliasing=""All"" BorderlineDashStyle = ""Solid"" BorderlineColor = ""Gray""> <BorderSkin SkinStyle = ""Emboss"" /> <ChartAreas> <ChartArea Name=""Default"" _Template_=""All"" BackColor=""Wheat"" BackSecondaryColor=""White"" BorderColor=""64, 64, 64, 64"" BorderDashSolid"" ShadowColor=""Transparent""> </ChartArea> </ChartAreas> </Chart>"); chart.AddSeries( chartType: "Column", yValues: productList.Select(e => e.Price).ToArray(), xValue: productList.Select(e => e.Name).ToArray() ); chart.Write(); } 

We have created a sequence of Product objects to use as the data for the chart. We’ve lowered the price of the Stadium product so that it doesn’t dwarf the others when the chart is displayed.
我们已经生成了一个Product对象序列,以作为这个图表的数据。我们也降低了Stadium(露天体育场,原来的价格很高 — 译者注)产品的价格,以便显示图表时,不会使其它产品显得矮小。

We begin by creating a Chart object. The first two constructor parameters are the width and height of the image that we want to generate, expressed in pixels. The third argument is the tricky one. Almost all of the configuration options for the chart are expressed through an XML document. The underlying charting component is incredibly feature-rich. It supports 35 different chart styles, will render charts in 2D and 3D, and allows almost every aspect of the chart’s appearance to be configured. Unfortunately, all of that complexity ends up in the XML file and its hundreds of options.
我们从生成一个Chart对象开始。前面两个参数是我们希望生成的图像的宽度和高度,以像素表示。第三个参数是比较复杂的一个。几乎所有的chart配置选项都通过一个XML文档来表示。基本的作图组件难以置信地特性丰富。它支持35种不同的图表样式、呈现二维(2D)及三维(3D)图表、以及呈现图表的几乎每个方面都是可配置的。不幸的是,所有这种复杂性都在这个XML文件及其数百个选项中完成。

Once we have created the Chart object, we can use the AddSeries method to add individual data series. In this example, we have only one data series, which is the price of the Product objects. We specify the type of the chart with the chartType parameter, and the values for the X and Y axes using LINQ to select the properties we want. Notice that we convert the results of the LINQ queries to arrays to evaluate the queries.
一旦我们已经生成Chart对象,我们就可以用AddSeries方法来添加个别数据系列。在这个例子中,我们只有一个数据系列,即Product对象的价格。我们以chartType参数来指定这个图表的类型,而X和Y轴的值用LINQ去选择我们所要的属性。注意,我们把LINQ查询的结果转换成数组,以便求取查询的值。

The critical method is Chart.Write, which produces the chart as an image directly to the response. The image that is generated by the example in Listing 15-25 is shown in Figure 15-7.
评判方法是Chart.Write,它把这个图表绘制成一个图像直接发送到响应。由清单15-25生成的图像如图15-7所示。

图15-7

Figure 15-7. Creating a simple chart with the chart helper
图15-7. 以chart辅助器生成一个简单的图表

The easiest way to include a chart in a web page is via a partial view, which we describe in detail later in the chapter.
在一个web页面中包含一个图表最容易的办法是通过一个分部视图,我们在本章稍后会详细描述。

Using Other Built-In Helpers
使用其它内建辅助器

We’ve covered the most widely used of the built-in helpers, but there are others available as well. These are described in Table 15-11.
我们已经涉及了广为使用的内建辅助器,但还有一些其它辅助器。这些如表15-11所示。

Table 15-11. Other Built-in Helpers
表15-11. 其它内建辅助器
Helper
辅助器
Description
描述
CryptoProvides access to common cryptographic hashing functions
提供对常用的加密哈希函数的访问
WebMailSends e-mail via SMTP, as used in the PartyInvites application
通过SMTP发送e-mail,如PartyInvites应用程序中所用的那样
JsonEncodes and decodes JSON data
编码及解码JSON数据
ServerInfoProvides information about the server
提供关于服务器的信息
WebImageLoads images and performs basic image manipulation
装载图像并执行基本的图像处理

The reason that we haven’t provided examples of these helpers is that we don’t think you should use them. These are helpers from other technologies (such as WebMatrix and ASP.NET Web Forms) and perform operations that we think are better handled as code in the action method (the exception being the WebMail helper, which we demonstrated in Chapter 3). For example, if we need to generate JSON data, then we prefer to use the JsonResult action result type we described in Chapter 12. When we want to perform cryptographic functions, we prefer to use the extensive .NET Framework cryptographic support from within an action method.
我们没有提供这些辅助器示例的原因是我们不认为你应该使用它们。这些是来自于其它技术(如WebMatrix和ASP.NET的Web表单)的辅助器,而且,这些辅助器执行的是我们认为最好在动作方法中进行代码处理的操作(WebMail辅助器除外,我们在第7章演示过它)。例如,如果我们需要生成JSON数据,那么,我们更喜欢用我们在第12章描述的JsonResult动作结果类型。当我们想执行加密功能时,我们更喜欢在一个动作方法之中使用详尽的.NET框架的加密支持。

■ Tip The MVC source code, which you can download from http://codeplex.com/aspnet, contains some additional helpers in the Microsoft.Web.Helpers assembly. These include helpers to integrate with search engines, web analytics services, social networks, and more.
提示:MVC源代码,可以从http://codeples.com/aspnet下载,含有Microsoft.Web.Helpers程序集中的一些其它辅助器。这些包括了,与搜索引擎集成、web分析服务、社区网等方面的辅助器。

Using Sections
使用分段

The Razor engine supports the concept of sections, which allow you to provide regions of content within a layout. Razor sections give greater control over which parts of the view are inserted into the layout and where they are placed. Listing 15-26 shows a view that defines some sections.
Razor引擎支持分段的概念,这允许你在一个布局中提供内容区域。Razor分段能够很好地控制把哪部分视图插入到布局之中,以及把它们放到哪儿。清单15-26演示了一个定义了一些分段的视图。

Listing 15-26. Defining a Section in a View
清单15-26. 在一个视图中定义分段

@{ ViewBag.Title = "Index"; } <h4>This is the view</h4> @section Header { @foreach (string str in new [] {"Home", "List", "Edit"}) { <div style="float:left; padding:5px"> @Html.ActionLink(str, str) </div> } <div style="clear:both" /> } <h4>This is the view between the header and footer sections</h4> @section Footer { <h4>This is the footer</h4> } <h4>This is the view again</h4> 

You create sections using the Razor @section tag followed by a name for the section. In our example, we created sections called Header and Footer. The body of a section is just a fragment of Razor syntax and static markup.
你可以用Razor的@section标签跟随一个分段名的办法来生成分段。在我们的例子中,我们生成了名为Header和Footer的分段。分段体只是一个Razor语法和静态标记的片断。

Listing 15-27 shows a layout that uses our new sections.
清单15-27演示了一个使用了我们新分段的布局。

Listing 15-27. Using Sections in a Layout
清单15-27. 在布局中使用分段

<!DOCTYPE html> <html> <head> <title>@ViewBag.Title</title> <link href="http://archive.cnblogs.com/a/2376540/@Url.Content(" target="_blank" rel="nofollow"> @RenderSection("Header") <h4>This is the layout between the header and the body</h4> @RenderBody() <h4>This is the layout between the body and the footer</h4> @RenderSection("Footer") </body> </html> 

We insert the contents of a section into a layout using the RenderSection helper, passing in the name of the section. The parts of the view that are not contained in a section are available through the RenderBody helper. This helper inserts content from anywhere in the view that is not contained in a @section block, which is why we mingled our Header and Footer sections in Listing 15-27 with the rest of the content.
用RenderSection辅助器,在其中传递分段名,我们可以把一个分段的内容插入到一个布局。通过RenderBody辅助器,未包含在分段中的视图部分也是可用的。这个辅助器插入的内容就是视图中未包含在@section块的其余部分,这是为什么我们在清单15-27中把Header和Footer分段与其余内容混合在一起的原因。

Figure 15-8 shows how the layout and view render. You can see how the sections are extracted from the view and displayed where the RenderSection helpers appear in the layout. Notice also that the RenderBody helper has inserted all of the content that surrounds the sections.
图15-8演示了布局与视图是如何呈现的。你可以看出,如何从视图中提取分段内容,并显示在布局中RenderSection辅助器出现的地方。还要注意,RenderBody辅助器已经插入了分段周围的全部内容。

图15-8

Figure 15-8. Rendering a layout with sections
图15-8. 呈现带有分段的布局

■ Note A view can define only the sections that are referred to in the layout. The MVC Framework will throw an exception if you attempt to define sections in the view for which there is no corresponding @RenderSection helper call in the layout.
注:一个视图只可以定义在布局中引用的分段。如果你试图在视图中定义分段,但在布局中没有对应的@RenderSection辅助器调用,MVC框架会弹出异常。

Mixing the sections in with the rest of the view is unusual. The convention is to define the sections at either the start or the end of the view, to make it easier to see which regions of content will be treated as sections and which will be captured by the RenderBody helper. Another approach is to define the view solely in terms of sections, including one for the body, as shown in Listing 15-28.
把分段与视图的其余部分混合在一起是不常用的。其约定是在视图的开始或结尾部分定义分段,以便更容易看到哪些内容区域被处理成分段,以及哪些将要由RenderBody辅助器来捕捉。另一个办法是定义一个独立的分段视图,并包括一个用于体的分段,如清单15-28所示。

Listing 15-28. Defining a View in Terms of Razor Sections
清单15-28. 定义一个Razor的分段视图

@{ ViewBag.Title = "Index"; } @section Header { @foreach (string str in new [] {"Home", "List", "Edit"}) { <div style="float:left; padding:5px"> @Html.ActionLink(str, str) </div> } <div style="clear:both" /> } @section Body { <h4>This is the body section</h4> } @section Footer { <h4>This is the footer</h4> } 

We find this makes for clearer views and reduces the chances of extraneous content being captured by RenderBody. Of course, instead of RenderBody, we could have used RenderSection("Body"), as shown in Listing 15-29.
我们发现,这有利于更清晰的视图,并减少了RenderBody捕捉无关内容的可能性。当然,我们已经用RenderSection(“Body”)来代替RenderBody,如清单15-29所示。

Listing 15-29. Using RenderSection(“Body”) to Define a View
清单15-29. 使用RenderSection(“Body”)定义一个视图

... <body> @RenderSection("Header") <h4>This is the layout between the header and the body</h4> @RenderSection("Body") <h4>This is the layout between the body and the footer</h4> @RenderSection("Footer") </body> ... 

Testing For Sections
对分段进行检测

You can check to see if a view has defined a specific section from the layout. This is a useful way to provide default content for a section if a view doesn’t need or want to provide specific content. Listing 15-30 provides an example.
你可以考查,一个视图是否定义了布局中指定的分段。如果一个视图不需要某个特定内容,或想提供一个特定内容,为一个分段提供一个默认内容是一种很有用的办法。清单15-30提供了一个示例。

Listing 15-30. Checking Whether a Section Is Defined in a View
清单15-30. 检查在一个视图中是否定义了一个分段

... @if (IsSectionDefined("Footer")) { @RenderSection("Footer") } else { <h4>This is the default footer</h4> } ... 

The IsSectionDefined helper takes the name of the section you want to check and returns true if the view you are rendering defines that section. In the example, we use this helper to determine if we should render some default content when the view doesn’t define the Footer section.
IsSectionDefined辅助器以你想检查的分段名为参数,如果你呈现的视图定义了这个分段,返回true。在这个例子中,我们用这个辅助器检测了,当视图未定义Footer分段时,我们是否应该显示默认内容。

Rendering Optional Sections
呈现可选分段

An alternative to testing to see if a section is defined is to make the section optional, as shown in Listing 15-31.
检测是否定义了一个分段的另一用途是使分段可选,如清单15-31所示。

Listing 15-31. Rendering an Optional Section
清单15-31. 呈现可选分段

... <body> @RenderSection("Header") <h4>This is the layout between the header and the body</h4> @RenderSection("Body") <h4>This is the layout between the body and the footer</h4> @RenderSection("Footer", false) </body> ... 

The overloaded version of RenderSection takes a parameter that specifies whether a section is required. By passing in false, we make the section optional, so that the layout will include the contents of the Footer section if the view defines it, but will not throw an exception if it doesn’t.
RenderSection的过载版本以一个参数来指定分段是否是必须的。通过在其中传递false,我们实现了这个分段可选,因此,如果视图定义了Footer,布局将包含Footer的内容,但如果未定义,将不会弹出异常。

Using Partial Views
使用分部视图

We often want to use the same fragments of Razor markup in several different places. Rather than duplicate the markup, we use partial views. These are separate view files that contain fragments of markup and can be included in other views.
我们经常想在几个不同的地方使用同样的Razor标记片段。我们并不是重复这些标记,而是采用分部视图。这些是含有标记片段的独立的视图文件,且可以被包含在其它视图之中。

In this section, we will show you how to create and use partial views, explain how they work, and demonstrate the techniques available for passing view data to a partial view.
在本小节中,我们将向你演示如何生成和使用分部视图,解释它们如何工作,并演示把视图数据传递给分部视图的技术。

Creating a Partial View
生成一个分部视图

You create a partial view by right-clicking inside a folder under /Views, selecting Add → View from the pop-up menu, and making sure that the Create as Partial View option is checked, as shown in Figure 15-9.
通过右击/Views下的一个文件夹,从弹出菜单中选择“添加” → “视图”,并确保复选了“创建分部视图”检查框,你可以创建一个分部视图。如图15-9所示。

图15-9

Figure 15-9. Creating a partial view
图15-9. 创建一个分部视图

A partial view is empty when it is created. We used the partial view to add a simple message to our layout. We use a partial method through the Html.Partial helper, as demonstrated in Listing 15-32.
生成的分部视图是空的。我们用这个分部视图把一个简单的消息添加到我们的布局。我们通过Html.Partial辅助器来使用partial方法,如清单15-32所示。

Listing 15-32. Using the Partial Helper to Render a Partial Method(这里应当是View — 译者注)
清单15-32. 使用Partial辅助器呈现一个分部视图

@{ ViewBag.Title = "Partial Views"; } <p>This is the method rendered by the action method</p> @Html.Partial("MyPartial") <p>This is the method rendered by the action method again</p> 

The parameter we passed to the Partial helper is the name of the partial view to render. The MVC Framework renders the view and adds the content that is generated to the response to the client, as shown in Figure 15-10.
我们传递给Partial辅助器的参数是要呈现的分部视图名。MVC框架呈现该视图,并把所生成的内容添加到客户端响应,如图15-10所示。

■ Tip The Razor View Engine looks for partial views in the same way that it looks for regular views (in the ~/Views/<controller> and ~/Views/Shared folders). This means that you can create specialized versions of partial views that are controller-specific and override partial views of the same name in the Shared folder. This may seem like an odd thing to do, but one of the most common uses of partial views is to render content in layouts, and this feature can be very handy.
提示:Razor视图引擎对分部视图的查找方式与规则视图的查找方式相同(在~/Views/<控制器>和~/Views/Shared文件夹中)。这意味着你可以创建特殊版本的分布视图,它们是控制器专用的,并且覆盖Shared文件夹中同名的分部视图。这看起来似乎是在做奇怪的事情,但分部视图最普通的运用之一是在布局中呈现内容,而且这一特性可以十分灵活。

图15-10

Figure 15-10. Rendering a partial view
图15-10. 呈现分部视图

Using Strongly Typed Partial Views
使用强类型分部视图

You can create strongly typed partial views, and then pass view model objects to be used when the partial view is rendered. To create a strongly typed partial view, simply check the Create a strongly-typed view option and enter the view model type. Figure 15-11 shows the Add View dialog box for a partial view called MyStronglyTypedPartial, which uses an IEnumerable<string> as its view model type.
你可以创建强类型分部视图,然后在呈现这个分部视图时,传递要使用的视图模型对象。要创建强类型分部视图,只要简单地点击“创建强类型视图”选项,并输入其视图模型类型。图15-11显示了分部视图名为MyStronglyTypedPartial的“添加视图”对话框,它以IEnumerable<string>作为其视图模型类型。

图15-11

Figure 15-11. Creating a strongly typed partial view
图15-11. 创建强类型分部视图

The contents of a strongly typed partial view are as you would expect. Listing 15-33 shows a simple example. Once again, we have explicitly disabled the use of layouts.
强类型分部视图的内容如你期望的那样。清单15-33演示了一个简单的例子。再一次地,我们已经明确地取消了使用布局。

Listing 15-33. A Strongly Typed Partial View
清单15-33. 一个强类型分部视图

@model IEnumerable<string> <p>This is my strongly-typed partial view</p> <ul> @foreach (string val in Model) { <li>@val</li> } </ul> 

This view uses a foreach loop to create list items for each element in the view model enumeration.
这个视图使用了一个foreach循环,对视图模型枚举中的每个元素生成了列表项。

By default, a strongly typed partial view will inherit the view model object from a strongly typed parent view. If you don’t want to propagate the view model from the parent, or you are rendering the partial view from a weakly typed view, then you must pass a view model object as a parameter to the Html.Partial helper, as shown in Listing 15-34.
默认地,强类型分部视图将从强类型父视图继承视图模型对象。如果你不想从父视图传播视图模型,或你是在呈现一个弱类型视图的分部视图,那么你必须把一个视图模型对象作为参数传递给Html.Partial辅助器,如清单15-34所示。

Listing 15-34. Rendering a Strongly Typed Partial View
清单15-34. 呈现一个强类型分部视图

@{ ViewBag.Title = "Partial Views"; } <p>This is the method rendered by the action method</p> @Html.Partial("MyStronglyTypedPartial", new [] {"Apples", "Mangoes", "Oranges"}) <p>This is the method rendered by the action method again</p> 

Rendering partial views doesn’t invoke an action method (see the next section if that is what you need), so the view model object for the partial view must originate in the parent view. In this example, we have passed a string array as the view model object to the partial view. The result of rendering the view in Listing 15-35 (and, therefore, implicitly the partial view in Listing 15-34) is shown in Figure 15-12.
呈现分部视图并不调用动作方法(如果需要,参阅下一小节),因此,分部视图的视图模型对象必须在父视图中创建。在这个例子中,我们已经把一个字符串数组作为视图模型对象传递给分部视图。清单15-35视图的呈现结果如图15-12所示。

图15-12

Figure 15-12. Rendering a strongly typed partial view
图15-12. 呈现强类型分部视图

Using Child Actions
使用子动作

Child actions are action methods invoked from within a view. This lets you avoid repeating controller logic that you want to use in several places in the application. Child actions are to actions as partial views are to views.
子动作是在一个视图中调用的动作方法。当你想把某种控制器逻辑运用于应用程序的几个地方时,子动作可以让你避免重复的控制器逻辑。子动作与动作之间的关系如同分部视图与视图一样。

You might want to use a child action whenever you want to display some data-driven “widget” that appears on multiple pages and contains data unrelated to the main action that is running. We used this technique in the SportsStore example to include a data-driven navigation menu on every page, without needing to supply the navigation data directly from every action method. The navigation data was supplied independently by the child action.
无论什么时候,当你想显示一些数据驱动的“小部件”,它们出现在多个页面上、而且含有与主动作无关的数据,或许你都可以使用子动作。我们在SportsStore例子中使用了这种技术,以便在每个页面包含一个数据驱动的导航菜单,而不需要直接通过每个动作方法来支持导航数据。这些导航数据是由子动作独立提供的。

Creating a Child Action
创建一个子动作

Any action can be used as a child action. To demonstrate the child action feature, we have defined the action method shown in Listing 15-35.
任何动作都可以用作为一个子动作。为了演示子动作特性,我们定义了一个动作方法,如清单15-35所示。

Listing 15-35. A Child Action
清单15-35. 一个子动作

[ChildActionOnly] public ActionResult Time() { return PartialView(DateTime.Now); } 

The ChildActionOnly attribute ensures that an action method can be called only as a child method from within a view. An action method doesn’t need to have this attribute to be used as a child action, but we tend to use this attribute to prevent the action methods from being invoked as a result of a user request.
ChildActionOnly性质确保了一个动作方法只能在一个视图中作为一个子动作进行调用。一个动作方法并不需要使用这个性质才能作为一个子动作,但我们倾向于使用这个性质,以防止这个动作方法作为用户请求的一个结果被调用。

Having defined an action method, we need to create what will be rendered when the action is invoked. Child actions are typically associated with partial views, although this is not compulsory. Listing 15-36 shows the Time.cshtml view we created for this demonstration.
定义了一个动作方法之后,我们需要创建这个动作被调用时要显示什么。子动作典型地用分部视图关联,尽管这不是必须的。清单15-36显示了我们为这个演示创建的Time.cshtml视图。

Listing 15-36. A Partial View for Use with a Child Action
清单15-36. 一个使用子动作的分部视图

@model DateTime <p>The time is: @Model.ToShortTimeString()</p> 

Rendering a Child Action
呈现子动作

You can invoke a child action using the Html.Action helper. With this helper, the action method is executed, the ViewResult is processed, and the output is injected into the response to the client. Listing 15-37 shows a Razor view that calls the action in Listing 15-35.
你可以用Html.Action辅助器来调用一个子动作。利用这个辅助器,这个动作方法被执行,ViewResult被得到了处理,于是其输出被注入到对客户端响应。清单15-37演示了调用清单14-35动作的一个Razor视图。

Listing 15-37. Calling a Child Action
清单15-37. 调用一个子动作

@{ ViewBag.Title = "Child Action Demonstration"; } <p>This is the method rendered by the action method</p> @Html.Action("Time") <p>This is the method rendered by the action method again</p> 

You can see the effect of rendering this view in Figure 15-13.
你可以通过图15-13看到这个视图呈现的结果。

图15-13

Figure 15-13. Using a child action
图15-13. 使用一个子动作

When we called the Action helper in Listing 15-38(应当是15-37 — 译者注), we provided a single parameter that specified the name of the action method to invoke. This causes the MVC Framework to look for an action method in the controller that is handling the current request. To call action methods in other controllers, provide the controller name, like this:
当我们在清单15-37中调用Action辅助器时,我们提供一个单一的参数,以指定要调用的动作方法名。这会引发MVC框架在处理当前请求的控制器中查找一个动作方法。为了调用其它控制器中的动作方法,要提供控制器名,像这样:

@Html.Action("Time", "MyController") 

You can pass parameters to action methods by providing an anonymously typed object whose properties correspond to the names of the child action method parameters. So, for example, if we have an child action method like this:
通过提供一个匿名对象,其属性对应于子动作方法的参数名,你可以把参数传递给动作方法。例如,如果我们有一个像这样的子动作方法:

[ChildActionOnly] public ActionResult Time(DateTime time) { return PartialView(time); } 

then we can invoke it from a view as follows:
那么,我们可以在一个视图中像下面这样调用它:

@Html.Action("Time", new { time = DateTime.Now }) 

Summary
小结

In this chapter, we explored the details of the MVC view system and the Razor View Engine. You have seen the different techniques available for inserting dynamic content into a view, and taken a tour of the built-in helper methods. We demonstrated how to avoid duplication by using partial views and child actions, and how to inject content into layouts using sections.
在本章中,我们考查了MVC的视图系统以及Razor视图引擎的细节。你看到了可用来把动态内容插入到一个视图之中的不同技术,并浏览了内建的辅助器方法。我们演示了如何通过运用分部视图和子动作来避免重复,以及如何用分段把内容注入到布局之中。

At this point in the book, you should have a solid knowledge of routing, controllers, actions, and views. In the next chapter, we delve into the M in MVC—models.
本书到了这里,你应该具备了路由、控制器、动作、以及视图的坚实基础。在下一章中,我们将深入到MVC的M — 模型之中。

posted on 2012-06-13 21:02  lucky.net  阅读(371)  评论(0编辑  收藏  举报

导航

Copyright luckynet 2013