ITPub博客

首页 > Linux操作系统 > Linux操作系统 > ASP.NET MVC Unleashed (6) (续)

ASP.NET MVC Unleashed (6) (续)

原创 Linux操作系统 作者:geez 时间:2009-03-21 20:20:15 0 删除 编辑
Creating Custom HTML Helpers
The ASP.NET MVC framework ships with a limited number of HTML helpers. The members of the ASP.NET MVC team identified the most common scenarios in which you would need a helper and focused on creating helpers for these scenarios.
Fortunately, creating new HTML helpers is a very easy process. You create a new HTML helper by creating an extension method on the HtmlHelper class. For example, Listing 7 contains a new Html.SubmitButton() helper that renders an HTML form. submit button.
创建自定义的HTML helpers
ASP.NET MVC框架自带了少量的HTML helpers。ASP.NET MVC团队成员挑出了一些最常见的场景,在这些场景中用户会很需要利用helper,团队成员致力于为这些使用场景创建helper。
幸运的是,创建新的HTML helper是很简单的过程。只需为HtmlHelper类创建一个扩展方法(extension method),您便创建了一个HTML helper。例如,Listing 7包含一个新的Html.SubmitButton() helper,在页面上绘制一个HTML表单提交按钮。
Listing 7 – Helpers\SubmitButtonHelper.cs [C#]
1. using System;  
2. using System.Web.Mvc;  
3.  
4. namespace Helpers  
5. {  
6.    public static class SubmitButtonHelper  
7.    {  
8.        ///  
9.        /// Renders an HTML form. submit button  
10.        ///  
11.        public static string SubmitButton(this HtmlHelper helper, string buttonText)  
12.        {  
13.            return String.Format("", buttonText);  
14.        }  
15.  
16.    }  
17. }  

Listing 7 contains an extension method named SubmitButton(). The SubmitButton() helper simply returns a string that represents an HTML tag.
Because the SubmitButton() method extends the HtmlHelper class, this method appears as a method of the HtmlHelper class in Intellisense (see Figure 6).

Figure 6 – Html.SubmitButton() Html helper included in Intellisense

clip_image012 

The view in Listing 8 uses the new Html.SubmitButton() helper to render the submit button for a form. Make sure that you import the namespace associated with your helper or the helper won’t appear in Intellisense. The correct namespace is imported in Listing 8 with the help of the <%@ Import %> directive.
*** Begin Note ***
As an alternative to registering a namespace for a particular view with the <%@ Import %> directive, you can register a namespace for an entire application in the system.web.pages.namespaces section of the web configuration (web.config) file.
*** End Note ***
Listing 7包含一个名为SubmitButton()的扩展方法。SubmitButton() helper只是简单地返回一个字符串,代表HTML 标记。由于SubmitButton() 方法是对HtmlHelper类的扩展,在智能提示(Intellisense)下该方法便显示为HtmlHelper类的一个方法了(见图6)。
Listing 8中的视图使用了这个新的Html.SubmitButton() helper,在表单上绘制了一个提交按钮。请确认您已经导入了helper所在的名称空间,否则这个helper将不会出现在智能提示中。Listing 8通过<%@ Import %>指示字导入了正确的名称空间。
【注意】用<%@ Import %>指示字为某个特定视图导入名称空间的另一个做法是,可以在web配置文件(web.config)的system.web.pages.namespaces配置节中注册一个整个应用程序作用域的名称空间。
Listing 8 – Views\Customer\Create.aspx
1. <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>  
2. <%@ Import Namespace="Helpers" %>  
3.  
4.  
5.    <% using (Html.BeginForm()) {%>  
6.  
7.        
 
8.            Fields  
9.            

 

10.                  
11.                <%= Html.TextBox("FirstName") %>  
12.            

 
13.            

 

14.                  
15.                <%= Html.TextBox("LastName") %>  
16.            

 
17.            

 

18.                <%= Html.SubmitButton("Create Customer") %>  
19.            

 
20.          
21.  
22.    <% } %>  
23.  
24.  

*** Begin Note ***
All of the standard HTML helpers, such as the Html.TextBox() helper, are also implemented as extension methods. This means that you can swap the standard set of helpers for a custom set of helpers.
*** End Note ***
【注意】所有标准的HTML helpers,如Html.TextBox() helper,也是通过扩展方法(extension method)的机制实现的,这意味着您可以用自定义的helper去替换那些标准的helper。

Using the TagBuilder Class
The TagBuilder class is a utility class included in the ASP.NET MVC framework that you can use when building HTML helpers. The TagBuilder class, as it name suggests, makes it easier to build HTML tags.
Here’s a list of the methods of the TagBuilder class:
• AddCssClass() – Enables you to add a new class=”” attribute to a tag.
• GenerateId() – Enables you to add an id attribute to a tag. This method automatically replaces periods in the id (by default, periods are replaced by underscores)
• MergeAttribute() – Enables you to add attributes to a tag. There are multiple overloads of this method.
• SetInnerText() – Enables you to set the inner text of the tag. The inner text is HTML encode automatically.
• ToString() – Enables you to render the tag. You can specify whether you want to create a normal tag, a start tag, an end tag, or a self-closing tag.
使用TagBuilder类
TagBuilder类是一个包含在ASP.NET MVC框架中的一个实用工具类,在创建HTML helper时可以利用它。正如其名称的含义一样,TagBuilder类使创建HTML标记(tags)变得更容易。
下面列出了TagBuilder类的一些方法:
• AddCssClass() – 可以用它向一个标记(tag)中添加一个新的class属性(class=”” ).
• GenerateId() – 可以用它来给一个标记添加id属性。该方法会自动替换id值中的句点(默认情况下将句点替换为下划线,.->_)
• MergeAttribute() – 可以用它来给一个标记添加多个属性. 该方法有多个重载.
• SetInnerText() – 可以用它来设置一个标记的inner text。自动对该inner text进行 HTML编码.
• ToString() –可以用它来输出标记(tag),您可以指定是否希望创建一个普通标记、一个起始标记、一个结束标记、或自闭的标记(即标记以/>结束).

The TagBuilder class has four important properties:
• Attributes – Represents all of the attributes of the tag.
• IdAttributeDotReplacement – Represents the character used by the GenerateId() method to replace periods (the default is an underscore).
• InnerHTML – Represents the inner contents of the tag. Assigning a string to this property does notHTML encode the string.
• TagName – Represents the name of the tag.
These methods and properties give you all of the basic methods and properties that you need to build up an HTML tag. You don’t really need to use the TagBuilder class. You could use a StringBuilder class instead. However, the TagBuilder class makes your life a little easier.
The helper in Listing 9, the Html.ImageLink() helper, is created with a TagBuilder. The Html.ImageLink() helper renders an image link.
TagBuilder类有四个重要的属性:
• Attributes – 表示一个标记(tag)的所有属性.
• IdAttributeDotReplacement – 表示GenerateId()方法用来替换句点的字符(默认是下划线).
• InnerHTML – 表示一个标记(tag)的内含内容,将一个串赋给给属性时,不会自动进行HTML编码.
• TagName – 表示标记(tag)名.
在创建一个HTML标记(tag)时,这些方法和属性向您提供了所有基本途径。其实您并不一定需要使用TagBuilder类,您可以使用StringBuilder类来代替。然而,使用TagBuilder类会更容易一些。
Listing 9列出的Html.ImageLink() helper就是用TagBuilder创建的。Html.ImageLink() helper输出一个图像链接:
Listing 9 – Helpers\ImageLinkHelper.cs [C#]
1. using System.Web.Mvc;  
2. using System.Web.Routing;  
3.  
4. namespace Helpers  
5. {  
6.    public static class ImageLinkHelper  
7.    {  
8.        public static string ImageLink(this HtmlHelper helper, string actionName, string imageUrl, string alternateText)  
9.        {  
10.            return ImageLink(helper, actionName, imageUrl, alternateText, null, null, null);  
11.        }  
12.          
13.        public static string ImageLink(this HtmlHelper helper, string actionName, string imageUrl, string alternateText, object routeValues)  
14.        {  
15.            return ImageLink(helper, actionName, imageUrl, alternateText, routeValues, null, null);  
16.        }  
17.  
18.        public static string ImageLink(this HtmlHelper helper, string actionName, string imageUrl, string alternateText, object routeValues, object linkHtmlAttributes, object imageHtmlAttributes)  
19.        {  
20.            var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);  
21.            var url = urlHelper.Action(actionName, routeValues);  
22.  
23.            // Create link  
24.            var linkTagBuilder = new TagBuilder("a");  
25.            linkTagBuilder.MergeAttribute("href", url);  
26.            linkTagBuilder.MergeAttributes(new RouteValueDictionary(linkHtmlAttributes));  
27.  
28.            // Create image  
29.            var imageTagBuilder = new TagBuilder("img");  
30.            imageTagBuilder.MergeAttribute("src", urlHelper.Content(imageUrl));  
31.            imageTagBuilder.MergeAttribute("alt", urlHelper.Encode(alternateText));  
32.            imageTagBuilder.MergeAttributes(new RouteValueDictionary(imageHtmlAttributes));  
33.  
34.            // Add image to link  
35.            linkTagBuilder.InnerHtml = imageTagBuilder.ToString(TagRenderMode.SelfClosing);  
36.  
37.            return linkTagBuilder.ToString();  
38.        }  
39.    }  
40. }  

The Html.ImageLink() helper in Listing 9 has three overloads. The helper accepts the following parameters:
actionName – The controller action to invoke.
imageUrl – The URL of the image to display.
alternateText – The alt text to display for the image.
routeValues – The set of route values to pass to the controller action.
linkHtmlAttributes – The set of HTML attributes to apply to the link.
imageHtmlAttribute – The set of HTML attributes to apply to the image.
Listing 9中的Html.ImageLink() helper有三个重载方法,该helper接受以下参数:
actionName – 要调用的控制器行为.
imageUrl – 要显示的图像的URL.
alternateText – 要显示的图像的alt文字表示.
routeValues – 传递给控制器行为的一组路由参数值.
linkHtmlAttributes – 附加到该链接上的一组HTML属性.
imageHtmlAttribute – 附加到该图像上的一组HTML属性.

For example, you can render a delete link by calling the Html.ImageLink() helper like this:
[C#]
<%= Html.ImageLink("Delete", "~/Content/Delete.png", "Delete Account", new {AccountId=2}, null, new {border=0}) %>
Two instances of the TagBuilder class are used in Listing 9. The first TagBuilder is used to build up the link tag. The second TagBuilder is used to build up the image tag.
Notice that an instance of the UrlHelper class is created. Two methods of this class are called. First, the UrlHelper.Action() method is used to generate the link to the controller action.
Second, the UrlHelper.Content() method is used to convert an application relative path into a full relative path. For example, if your application is named MyApplication then the UrlHelper.Content() method would convert the application relative path “~/Content/Delete.png” into the relative path “/MyApplication/Content/Delete.png”.
例如,可以按下述方式调用Html.ImageLink() helper来输出一个删除链接:
[C#]
<%= Html.ImageLink("Delete", "~/Content/Delete.png", "Delete Account", new {AccountId=2}, null, new {border=0}) %>
Listing 9中使用了TagBuilder类的两个实例,第一个TagBuilder实例用来构造链接标记;第二个TagBuilder实例用来构件图像标记。
请注意,例中创建了UrlHelper类的一个实例,并调用了该类的两个方法。首先,调用UrlHelper.Action()方法来生成一个指向特定控制器行为的链接。第二,调用了UrlHelper.Content()方法,将应用相对路径转换为完整的相对路径。例如,如果您的应用名称为MyApplication,那么,UrlHelper.Content()方法会将应用相对路径“~/Content/Delete.png”转换为“/MyApplication/Content/Delete.png”相对路径。

Using the HtmlTextWriter Class
As an alternative to using the TagBuilder class to build up HTML content in an HTML helper, you can use the HtmlTextWriter class. Like the TagBuilder class, the HtmlTextWriter class has specialized methods for building up a string of HTML.
Here is a list of some of the more interesting methods of the HtmlTextWriter class (this is not a comprehensive list):
• AddAttribute() – Adds an HTML attribute. When RenderBeginTag() is called, this attribute is added to the tag.
• AddStyleAttribute() – Adds a style. attribute. When RenderBeginTag() is called, this style. attribute is added to the tag.
• RenderBeginTag() – Renders an opening HTML tag to the output stream.
• RenderEndTag() – Closes the last tag opened with RenderBeginTag().
• Write() – Writes text to the output stream.
• WriteLine() – Writes a newline to the output stream (good for keeping your HTML readable when you do a browser View Source).
For example, the HTML helper in Listing 10 uses the HtmlTextWriter to create a bulleted list.
使用HtmlTextWriter类
作为TagBuilder类的一个替代,也可以在一个HTML helper中,使用HtmlTextWriter类来构造HTML内容。和TagBuilder相似,HtmlTextWriter类有一些专用方法来构造HTML串。
• AddAttribute() – 添加一个HTML属性,当调用RenderBeginTag()时,该属性便被加入到标记中。
• AddStyleAttribute() – 添加一个样式属性,当调用RenderBeginTag()时,该样式属性便被加入到标记中。
• RenderBeginTag() – 往输出流中写一个HTML标记的开始.
• RenderEndTag() – 关闭通过RenderBeginTag()打开的标记(即结束标记).
• Write() – 将文本写入输出流.
• WriteLine() – 往输出流中写一个新行标志(当用浏览器的查看页面源码时,显示出的 HTML文本更易读).
例如,Listing 10中的HTML helper使用HtmlTextWriter创建一个显示项目编号的列表。
Listing 10 – Helpers\BulletedListHelper.cs [C#]
1. using System;  
2. using System.Collections;  
3. using System.IO;  
4. using System.Web.Mvc;  
5. using System.Web.UI;  
6.  
7. namespace Helpers  
8. {  
9.    public static class BulletedListHelper  
10.    {  
11.        public static string BulletedList(this HtmlHelper helper, string name)  
12.        {  
13.            var items = helper.ViewData.Eval(name) as IEnumerable;  
14.            if (items == null)  
15.                throw new NullReferenceException("Cannot find " + name + " in view data");  
16.  
17.            var writer = new HtmlTextWriter(new StringWriter());  
18.              
19.            // Open UL  
20.            writer.RenderBeginTag(HtmlTextWriterTag.Ul);  
21.            foreach (var item in items)  
22.            {  
23.                writer.RenderBeginTag(HtmlTextWriterTag.Li);  
24.                writer.Write(helper.Encode(item));  
25.                writer.RenderEndTag();  
26.                writer.WriteLine();  
27.            }  
28.            // Close UL  
29.            writer.RenderEndTag();  
30.  
31.            // Return the HTML string  
32.            return writer.InnerWriter.ToString();  
33.        }  
34.    }  
35. }  

The list of customers is retrieved from view state with the help of the ViewData.Eval() method. If you call ViewData.Eval(“Customers”), the method attempts to retrieve an item from the view data dictionary named Customers. However, if the Customers item cannot be retrieved from the view data dictionary, then the method attempts to retrieve the value of a property with the name Customers from the view data model (the view data dictionary take precedence over the view data model).
The HtmlTextWriter class is used to render the HTML
    and
  • tags needed to create the bulleted list. Each item from the items collection is rendered into the list.
You can call the Html.BulletedList() helper in a view like this:
<%= Html.BulletedList("Customers") %>
You can add the list of customers to view data with the controller action in Listing 11.
利用ViewData.Eval()方法从view state(视图状态)中获取了客户清单。如果调用ViewData.Eval(“Customers”),则该方法视图从view data字典中获取一个名为Customers的数据项。然而,倘若Customers数据项无法从view data字典中读取,则该方法视图从view data model(视图数据模型)中读取一个名为Customers的属性(view data dictionary视图数据字典优先于view data model视图数据模型)。
用HtmlTextWriter类来输出创建符号列表所需要的HTML
  • 标记。每个数据项均输出到列表里。
可以在视图中这样调用Html.BulletedList() helper:
<%= Html.BulletedList("Customers") %>
Listing 11中的控制器行为往view data(视图数据)里添加了客户清单。
Listing 11 – Controllers\CustomerController.cs [C#]
1. public ActionResult List()  
2. {  
3.    ViewData["Customers"] = from c in _entities.CustomerSet   
4.                            select c.LastName;   
5.    return View();  
6. }  

When you invoke the List() action, a list of customer last names are added to view state. When you call the Html.BulletedList() helper method, you get the bulleted list displayed in Figure 7.
There are multiple ways that you can build up HTML content within an HTML helper. You can use the TagBuilder class, the HtmlTextWriter class, or even the StringBuilder class. The choice is entirely a matter of preference.
当调用List()行为时,用客户的姓生成一个清单并加入到view state中。当调用Html.BulletedList() helper方法时,就会得到如图7所示的符号列表。

Figure 7 – Using the Html.BulletedList HTML helper

clip_image014

在一个HTML helper中,有多种方式可以用来构造HTML内容。可以使用TagBuilder类、HtmlTextWriter类、甚至是StringBuilder。选择哪一种完全属于个人偏好。

Creating a DataGrid Helper
In this section, we tackle a more complicated HTML helper: we build an Html.DataGrid() helper that renders a list of database records in an HTML table. We start with the basics and then we add sorting and paging to our Html.DataGrid() helper.
The basic Html.DataGrid() helper is contained in Listing 12.
创建一个DataGrid Helper
在本节里,我们来完成一个更为复杂的HTML helper:我们创建一个Html.DataGrid() helper,它将数据库中的记录列表输出为一个HTML表(table)。我们从基本做起,然后逐步往Html.DataGrid() helper中添加排序、分页等功能。
Listing 12中给出了基本的Html.DataGrid() helper。
Listing 12 – Helpers\DataGridHelperBasic.cs [C#]
1. using System;  
2. using System.Collections.Generic;  
3. using System.IO;  
4. using System.Linq;  
5. using System.Web.Mvc;  
6. using System.Web.UI;  
7.  
8. namespace Helpers  
9. {  
10.  
11.    public static class DataGridHelper  
12.    {  
13.        public static string DataGrid(this HtmlHelper helper)  
14.        {  
15.            return DataGrid(helper, null, null);  
16.        }  
17.  
18.        public static string DataGrid(this HtmlHelper helper, object data)  
19.        {  
20.            return DataGrid(helper, data, null);  
21.        }  
22.  
23.  
24.        public static string DataGrid(this HtmlHelper helper, object data, string[] columns)  
25.        {  
26.            // Get items  
27.            var items = (IEnumerable)data;  
28.            if (items == null)  
29.                items = (IEnumerable)helper.ViewData.Model;  
30.  
31.  
32.            // Get column names  
33.            if (columns == null)  
34.                columns = typeof(T).GetProperties().Select(p => p.Name).ToArray();  
35.  
36.            // Create HtmlTextWriter  
37.            var writer = new HtmlTextWriter(new StringWriter());  
38.  
39.            // Open table tag  
40.            writer.RenderBeginTag(HtmlTextWriterTag.Table);  
41.  
42.            // Render table header  
43.            writer.RenderBeginTag(HtmlTextWriterTag.Thead);  
44.            RenderHeader(helper, writer, columns);  
45.            writer.RenderEndTag();  
46.  
47.            // Render table body  
48.            writer.RenderBeginTag(HtmlTextWriterTag.Tbody);  
49.            foreach (var item in items)  
50.                RenderRow(helper, writer, columns, item);  
51.            writer.RenderEndTag();  
52.  
53.            // Close table tag  
54.            writer.RenderEndTag();  
55.  
56.            // Return the string  
57.            return writer.InnerWriter.ToString();  
58.        }  
59.  
60.  
61.        private static void RenderHeader(HtmlHelper helper, HtmlTextWriter writer, string[] columns)  
62.        {  
63.            writer.RenderBeginTag(HtmlTextWriterTag.Tr);  
64.            foreach (var columnName in columns)  
65.            {  
66.                writer.RenderBeginTag(HtmlTextWriterTag.Th);  
67.                writer.Write(helper.Encode(columnName));  
68.                writer.RenderEndTag();  
69.            }  
70.            writer.RenderEndTag();  
71.        }  
72.  
73.  
74.        private static void RenderRow(HtmlHelper helper, HtmlTextWriter writer, string[] columns, T item)  
75.        {  
76.            writer.RenderBeginTag(HtmlTextWriterTag.Tr);  
77.            foreach (var columnName in columns)  
78.            {  
79.                writer.RenderBeginTag(HtmlTextWriterTag.Td);  
80.                var value = typeof(T).GetProperty(columnName).GetValue(item, null) ?? String.Empty;  
81.                writer.Write(helper.Encode(value.ToString()));  
82.                writer.RenderEndTag();  
83.            }  
84.            writer.RenderEndTag();  
85.        }  
86.  
87.    }  
88. }  

In Listing 12, the Html.DataGrid() helper method has three overloads. All three overloads are generic overloads. You must supply the type of object – for example, Product – that the Html.DataGrid() should render.
Here are some examples of how you can call the html.DataGrid() helper:
[C#]
<%= Html.DataGrid()%>
<%= Html.DataGrid(ViewData["products"]) %>
<%= Html.DataGrid(Model, new string[] {"Id", "Name"})%>
In the first case, the Html.DataGrid() helper renders all of the items in the view data model into an HTML table. All of the public properties of the Product class are rendered in each row of the HTML table.
In the second case, the contents of the products item in view data is rendered into an HTML table. Again all of the public properties of the Public class are rendered.
In the third case, once again, the contents of the view data model are rendered into an HTML table. However, only the Id and Name properties are rendered (see Figure 8).
Listing 12中,Html.DataGrid() helper方法有三个重载,所有这三个重载方法都是泛型重载方法,调用时必须提供需要Html.DataGrid()来输出的对象类型(例如Product)。
以下是一些例子,显示了如何来调用html.DataGrid() helper:
在第一个例子里,Html.DataGrid() helper将view data model(视图数据模型)中的所有数据项输出到一个HTML表中。Product类的所有公共属性均输出为HTML表的一行。
第二个例子里,view data中的products字典项的内容输出为一个HTML表。和前面一样,依然是输出所有的公共属性。
第三个例子里,依旧是将view data model(视图数据模型)的内容输出为一个HTML表。然而,仅仅输出Id和Name两个属性(见图8)。

Figure 8 – Rendering an HTML table with the Html.DataGrid() helper

clip_image016 

The Html.DataGrid() helper uses an HtmlTextWriter to render the HTML table , , , ,
, and tags. Rendering these tags by taking advantage of the HtmlTextWriter results in cleaner and more readable code then using string concatenation (Please try to avoid string concatenation whenever possible!).
A tiny bit of reflection is used in the DataGrid() helper. First, reflection is used in the second DataGrid() method to retrieve the list of columns to display when no explicit list of columns is supplied to the helper:
[C#]
columns = typeof(T).GetProperties().Select(p => p.Name).ToArray();
Also, reflection is used to retrieve the value of a property to display within the RenderRow() method:
[C#]
var value = typeof(T).GetProperty(columnName).GetValue(item, null) ?? String.Empty;
*** Begin Note ***
Reflection is a .NET framework feature that enables you get information about classes, methods, and properties at runtime. You can even use reflection to dynamically load assemblies and execute methods at runtime.
*** End Note ***
The Html.DataGrid() helper displays any collection of items that implements the IEnumerable interface. For example, the controller action in Listing 13 assigns a set of products to the view data model property. This list of products can be displayed by the Html.DataGrid() helper.
Html.DataGrid() helper使用一个HtmlTextWriter实例来输出HTML表的相关标记,, , , ").Count);  
61.        }  
62.    }  
63.  
64.    public class Product  
65.    {  
66.        public int Id { get; set; }  
67.        public string Name { get; set; }  
68.        public decimal Price { get; set; }  
69.    }  
70. }  

If I want to feel completely confident about the Html.DataGrid() helper then I would need to write several more unit tests than the two tests contained in Listing 21. However, Listing 21 is a good start.
Both of the unit tests in Listing 21 take advantage of a utility method named CreateItems() that creates a list that contains a specific number of products.
Both unit tests also take advantage of the FakeHtmlHelper class from the MvcFakes project. When you call an HTML helper, you must supply an instance of the HtmlHelper class as the first parameter. The FakeHtmlHelper enables you to easily fake this helper.
*** Begin Note ***
Before you can run the tests in Listing 21, you must add a reference to the MvcFakes project to your Test project. The MvcFakes project is included in the CommonCode folder on the CD that accompanies this book.
*** End Note ***
如果我希望感觉上对Html.DataGrid() helper充满自信,那么我还得编写几个更多的单元测试,而不仅是Listing 21中包括的那两个测试。不过,Listing 21是一个好的起点。
Listing 21中的两个单元测试都利用了一个名为CreateItems()的助手方法,来创建一个包含特定数量产品的列表。
这两个单元测试也利用了MvcFakes 项目里的FakeHtmlHelper类。当调用一个HTML helper时,您必须将HtmlHelper类的一个实例所为第一个参数传递。FakeHtmlHelper是您能够很容易地伪造这个helper。
【注意】在运行Listing 21中的测试前,您必须在您的测试项目中添加一个到MvcFakes项目的引用。MvcFakes项目包含在本书所附CD的CommonCode文件夹中。

Summary
This chapter was devoted to the topic of HTML helpers. You learned how to create views more easily by using HTML helpers to render HTML content.
In the first part of this chapter, you learned how to use the standard set of HTML helpers included with the ASP.NET MVC framework. For example, you learned how to create HTML links with the Html.ActionLink() and Url.Action() helpers. You also learned how to use the form. helpers to render standard HTML form. elements such as dropdown lists and textboxes.
We also discussed how you can make your websites more secure against JavaScript. injection attacks by taking advantage of the Html.AntiForgeryToken() and Html.Encode() helpers.
In the next part of this chapter, we examined how you can create custom HTML helpers. We talked about the different utility classes that you can use when building a custom helper. In particular, you learned how to use the TagBuilder and HtmlTextWriter classes.
We then tackled building a real-world HTML helper. We created an Html.DataGrid() helper that renders database records in an HTML table. We added both sorting and paging support to our custom Html.DataGrid() helper.
In the final section of this chapter, you learned how to build unit tests for your custom HTML helpers. We created two unit tests for our Html.DataGrid() helper.
小结
本章集中讨论关于HTML helper的主题。您学习了如何通过使用HTML helper输出HTML内容,从而更容易地创建视图。
在本章的第一部分中,您学习了如何使用ASP.NET MVC框架中自身包含的标准HTML helpers。例如,您学习了如何用Html.ActionLink()和Url.Action() helpers来创建HTML链接,您也学习了如何使用表单helpers来输出标准的HTML表单元素,如下拉列表和输入文本框。
我们也讨论了如何通过利用Html.AntiForgeryToken()和Html.Encode() helpers来使您的站点更安全.、避免Javascript注入攻击。
在本章接下来的一部分中,我们研究了如何创建自定义HTML helpers,我们介绍了不同的实用工具类,您可以在创建自定义helper时使用它们。特别地,您学习了如何使用TagBuilder和HtmlTextWriter类。
然后,我们着手创建一个真实的HTML helper。我们创建了一个Html.DataGrid() helper,将数据库记录输出为一个HTML table,我们还为这个自定义Html.DataGrid() helper添加了排序和分页功能。
在本章最后一节里,您学习了如何为自定义HTML helper创建单元测试。我们为Html.DataGrid() helper创建了两个单元测试。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/740297/viewspace-573318/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2009-02-03

  • 博文量
    21
  • 访问量
    65699
, 和等。利用HtmlTextWriter来输出这些标记所产生的结果,比起用字符串连接操作(请尽可能地避免使用字符串连接操作!),代码更清晰、更易读。
在DataGrid() helper中使用了极少的反射机制,首先,当没有往helper中显式传递要显示的属性列表时,第二个DataGrid()方法用反射机制获取待显示的属性列表。
[C#]
columns = typeof(T).GetProperties().Select(p => p.Name).ToArray();
同样,反射机制也在RenderRow()方法中,被用来获取待显示的属性值:
[C#]
var value = typeof(T).GetProperty(columnName).GetValue(item, null) ?? String.Empty;
【注意】反射是.NET 框架的一个重要特性,可以用来在运行时获取关于类、方法、属性等相关信息,甚至可以利用反射机制在运行时刻动态加载运行库并调用其中的方法。
Html.DataGrid() helper能够显示所有实现了IEnumerable接口的数据集合。例如,Listing 13中的控制器行为将一个产品集合赋予了view data model(视图数据模型)属性,该产品集合可以用Html.DataGrid() helper显示出来。
Listing 13 – Controllers\ProductController.cs [C#]
1. using System.Linq;  
2. using System.Web.Mvc;  
3. using MvcApplication1.Models;  
4.  
5. namespace MvcApplication1.Controllers  
6. {  
7.    public class ProductController : Controller  
8.    {  
9.        private ToyStoreDBEntities _entities = new ToyStoreDBEntities();   
10.  
11.        public ActionResult Index()  
12.        {  
13.            return View(_entities.ProductSet.ToList());  
14.        }  
15.    }  
16. }  

Adding Sorting to the DataGrid Helper
Let’s make our Html.DataGrid() helper just a little more fancy. In this section, we’ll add sorting support. When you click a column header in the HTML table rendered by the Html.DataGrid() helper, the HTML table is sorted by the selected column (see Figure 9).
为DataGrid Helper添加排序功能
我们来把Html.DataGrid() helper做的更有趣些。在本节中,我们将加入排序支持。当您点击Html.DataGrid() helper 输出的HTML table的列首(column header)时,该HTML table将按照选定的列进行排序(见图9)。

Figure 9 – Html.DataGrid() with sorting support

clip_image018

In order to add the sorting support, we need to modify just one method of the existing DataGridHelper class. We need to modify the RenderHeader() method so that it renders links for headers. The modified RenderHeader() method is contained in Listing 14.
为了添加排序功能,我们仅需修改现有DataGridHelper类中的一个方法,我们需要修改RenderHeader()方法以使其把列首输出为链接。修改后的RenderHeader()方法如Listing 14所示。
Listing 14 – Helpers\DataGridHelperSorting.cs [C#]
1. private static void RenderHeader(HtmlHelper helper, HtmlTextWriter writer, string[] columns)  
2. {  
3.    writer.RenderBeginTag(HtmlTextWriterTag.Tr);  
4.    foreach (var columnName in columns)  
5.    {  
6.        writer.RenderBeginTag(HtmlTextWriterTag.Th);  
7.        var currentAction = (string)helper.ViewContext.RouteData.Values["action"];  
8.        var link = helper.ActionLink(columnName, currentAction, new {sort=columnName});  
9.        writer.Write(link);  
10.        writer.RenderEndTag();  
11.    }  
12.    writer.RenderEndTag();  
13.
 
The modified RenderHeader() method in Listing 14 creates a link for each header column by calling the HtmlHelper.ActionLink() method. Notice that the name of the header column is included as a route value in the link. For example, the following link is rendered for the Price header:
/Product/SortProducts?sort=Price
The actual database sorting happens within the Product controller. The SortProducts action in Listing 15 returns the products in different sort orders depending on the value of the sort parameter passed to the action.
Listing 14中经过修改的RenderHeader()方法通过调用HtmlHelper.ActionLink()方法,为每个列首创建一个链接。请留意,链接中将列首的名称作为路由值。例如,Price列首输出为下列链接:
/Product/SortProducts?sort=Price
数据库排序实际发生在Product控制器内。Listing 15中的SortProducts行为根据传递来的sort参数值,返回不同的排序结果。
Listing 15 – Controllers\ProductController.cs with SortProducts [C#]
1. public ActionResult SortProducts(string sort)  
2. {  
3.    IEnumerable products;  
4.    sort = sort ?? string.Empty;  
5.    switch (sort.ToLower())  
6.    {  
7.        case "name":  
8.            products = from p in _entities.ProductSet   
9.                       orderby p.Name select p;  
10.            break;  
11.        case "price":  
12.            products = from p in _entities.ProductSet  
13.                       orderby p.Price  
14.                       select p;  
15.            break;  
16.        default:  
17.            products = from p in _entities.ProductSet  
18.                       orderby p.Id  
19.                       select p;  
20.            break;  
21.    }  
22.  
23.    return View(products);  
24. }  

Adding Paging to the DataGrid Helper
It really wouldn’t be a proper Html.DataGrid() helper unless the helper supported paging. In this section, we modify our Html.DataGrid() helper so that it supports efficient paging through a large set of database records (see Figure 10).
为DataGrid Helper添加分页功能
除非Html.DataGrid() helper支持了分页,它才能算上是个不错的helper。本节里,我们对Html.DataGrid() helper进行修改,使之支持大容量数据库记录下的高效分页(见图10)。

Figure 10 – Paging through database records

clip_image020

In order to add paging support, we need to create two new supporting classes:
• PagedList – An instance of this class is passed to the Html.DataGrid() helper to represent a single page of records.
• PagingLinqExtensions – This class contains extension methods that extend the IQueryable interface with ToPagedList() methods that return a PagedList from a query.
The PagedList class is contained in Listing 16. This class
为添加分页支持,我们需要创建两个新的支持类:
• PagedList – 将该类的一个实例作为参数传递给Html.DataGrid() helper ,表示单页记录。
• PagingLinqExtensions – 该类包含ToPagedList()扩展方法,对IQueryable接口进行扩展,ToPagedList()扩展方法返回一个PagedList 实例。
Listing 16中包含了PagedList类。
Listing 16 – Paging\PagedList.cs [C#]
1. using System;  
2. using System.Collections.Generic;  
3.  
4. namespace Paging  
5. {  
6.    public class PagedList : List  
7.    {  
8.        public PagedList(IEnumerable items, int pageIndex, int pageSize, int totalItemCount, string sortExpression)  
9.        {  
10.            this.AddRange(items);  
11.            this.PageIndex = pageIndex;  
12.            this.PageSize = pageSize;  
13.            this.SortExpression = sortExpression;  
14.            this.TotalItemCount = totalItemCount;  
15.            this.TotalPageCount = (int)Math.Ceiling(totalItemCount / (double)pageSize);  
16.        }  
17.  
18.        public int PageIndex { get; set; }  
19.        public int PageSize { get; set; }  
20.        public string SortExpression { get; set; }  
21.        public int TotalItemCount { get; set; }  
22.        public int TotalPageCount { get; private set; }  
23.  
24.    }  
25. }  

The PagedList class inherits from the base generic List class and adds specialized properties for paging. The PageList class represents the following properties:
• PageIndex – The currently selected page (zero based).
• PageSize – The number of records to display per page.
• SortExpression – The column that determines the sort order of the records.
• TotalItemCount – The total number of items in the database.
• TotalPageCount – The total number of page numbers to display.
The second class, the PagingLinqExtensions class, extends the IQueryable interface to make it easier to return a PagedList from a LINQ query. The PagingLinqExtensions class is contained in Listing 17.
PagedList类从List泛型类继承,并加入了支持分页需要的特殊属性。PageList类包含以下一些属性:
• PageIndex – 当前选定的页 (以0为基数).
• PageSize – 每页显示的记录数.
• SortExpression – 记录的字段,决定记录如何排序.
• TotalItemCount – 数据库中记录的总数.
• TotalPageCount – 显示的页码总数.
第二个PagingLinqExtensions类从IQueryable 接口继承,从而能够容易地将一个LINQ查询结果以PagedList 形式返回。Listing 17中包含了PagingLinqExtensions 类。
Listing 17 – Paging\PagingLinqExtensions.cs [C#]
1. using System;  
2. using System.Linq;  
3.  
4. namespace Paging  
5. {  
6.    public static class PageLinqExtensions  
7.    {  
8.        public static PagedList ToPagedList  
9.            (  
10.                this IQueryable allItems,  
11.                int? pageIndex,  
12.                int pageSize  
13.            )  
14.        {  
15.            return ToPagedList(allItems, pageIndex, pageSize, String.Empty);  
16.  
17.        }  
18.  
19.        public static PagedList ToPagedList  
20.            (  
21.                this IQueryable allItems,  
22.                int? pageIndex,  
23.                int pageSize,  
24.                string sort  
25.            )  
26.        {  
27.            var truePageIndex = pageIndex ?? 0;  
28.            var itemIndex = truePageIndex * pageSize;  
29.            var pageOfItems = allItems.Skip(itemIndex).Take(pageSize);  
30.            var totalItemCount = allItems.Count();  
31.            return new PagedList(pageOfItems, truePageIndex, pageSize, totalItemCount, sort);  
32.  
33.        }  
34.    }  
35. }  

The PagingLinqExtensions class makes it possible to return a PagedList like this:
[C#]
var products = _entities.ProductSet
    .OrderBy(p => p.Id)
    .ToPagedList(page, 2);
Notice how you can call ToPagedList() directly on a LINQ query. The PagingLinqExtensions class simplifies your code.
Finally, we need to modify our Html.DataGrid() class to use the PagedList class to represent database records. The modified Html.DataGrid() class includes the new RenderPagerRow() method contained in Listing 18.
PagingLinqExtensions类使得像以下例子那样返回一个PagedList实例成为可能:
[C#]
var products = _entities.ProductSet
    .OrderBy(p => p.Id)
.ToPagedList(page, 2);
注意在例子的LINQ查询中是如何直接调用ToPagedList()的,PagingLinqExtensions简化了代码。
最后,我们需要修改Html.DataGrid()类,使用PagedList类来表示数据库记录。修改后的Html.DataGrid()类包括了新的RenderPagerRow()方法,如Listing 18所示。
Listing 18 – Helpers\DataGridHelperPaging.cs [C#]
1. private static void RenderPagerRow(HtmlHelper helper, HtmlTextWriter writer, PagedList items, int columnCount)  
2. {  
3.    // Don't show paging UI for only 1 page  
4.    if (items.TotalPageCount == 1)  
5.        return;  
6.  
7.    // Render page numbers  
8.    writer.RenderBeginTag(HtmlTextWriterTag.Tr);  
9.    writer.AddAttribute(HtmlTextWriterAttribute.Colspan, columnCount.ToString());  
10.    writer.RenderBeginTag(HtmlTextWriterTag.Td);  
11.    var currentAction = (string)helper.ViewContext.RouteData.Values["action"];  
12.    for (var i = 0; i < items.TotalPageCount; i++)  
13.    {  
14.        if (i == items.PageIndex)  
15.        {  
16.            writer.Write(String.Format("{0} ", i + 1));  
17.        }  
18.        else  
19.        {  
20.            var linkText = String.Format("{0}", i + 1);  
21.            var link = helper.ActionLink(linkText, currentAction, new { page = i, sort=items.SortExpression});  
22.            writer.Write(link + " ");               
23.        }  
24.    }  
25.    writer.RenderEndTag();  
26.    writer.RenderEndTag();  
27. }
  
The RenderPagerRow() method in Listing 18 renders the user interface for paging. This method simply renders a list of page numbers that act as hyperlinks. The selected page number is highlighted with an HTML tag.
The modified Html.DataGrid() helper requires an instance of the PagedList class for its data parameter. You can use the controller action in Listing 19 to add the right data to view state.
Listing 18中的RenderPagerRow()方法输出分页部分的用户界面,该方法仅简单地将一系列页码显示为数字的链接,选中的页码以一个HTML 标记加以强调。
Listing 19 – Controllers\ProductController.cs with PagedProducts [C#]
1. public ActionResult PagedProducts(int? page)  
2. {  
3.    var products = _entities.ProductSet  
4.        .OrderBy(p => p.Id).ToPagedList(page, 2);  
5.      
6.    return View(products);  
7. }  

*** Begin Warning ***
In Listing 19, notice that the ToPagedList() method is called on a LINQ query that includes a call to the OrderBy() method. When using the Entity Framework, you must order the results of a query before you can extract a page of records from the query.
*** End Warning ***
If you want to both page and sort the products, then you can use the controller action in Listing 20.
【注意】在Listing 19里,注意ToPagedList()方法是在一个LINQ查询中被调用的,跟随在OrderBy()方法后。当使用实体框架时,必须现对查询结果排序后,才能从中提取其中的一页记录。
如果既希望对产品进行排序,也希望支持分页,那么可以使用Listing 20中给出的控制器行为。
Listing 20 – Controllers\ProductController.cs with PagedSortedProducts [C#]
1. public ActionResult PagedSortedProducts(string sort, int? page)  
2. {  
3.    IQueryable products;  
4.    sort = sort ?? string.Empty;  
5.    switch (sort.ToLower())  
6.    {  
7.        case "name":  
8.            products = from p in _entities.ProductSet  
9.                       orderby p.Name  
10.                       select p;  
11.            break;  
12.        case "price":  
13.            products = from p in _entities.ProductSet  
14.                       orderby p.Price  
15.                       select p;  
16.            break;  
17.        default:  
18.            products = from p in _entities.ProductSet  
19.                       orderby p.Id  
20.                       select p;  
21.            break;  
22.    }  
23.  
24.    ViewData.Model = products.ToPagedList(page, 2, sort);  
25.    return View();  
26. }  

Notice that when you want to support sorting, you must pass the current sort column to the ToPageList() method. If you don’t pass the current sort column then clicking a page number causes the Html.DataGrid() to forget the sort order.
请注意,如果希望支持排序,那么必须将当前的排序列名传递给ToPageList()方法,如果没有传递它,那么点击排序页码将会导致Html.DataGrid()遗失之前的排序。

Testing Helpers
In general, you should place any complicated view logic in an HTML helper. There is a simple reason for this: you can test a helper but you cannot test a view.
The Html.DataGrid() helper that we created in the previous section is a good example of a helper that requires unit tests. There are several things that I could have gotten wrong while writing this helper. You should never trust anything that you write!
对helper进行测试
总体来说,只要视图逻辑比较复杂,您都应当将其封装在一个HTML helper中。这么做有个很简单的理由:您可以对helper进行测试,但不能对视图进行测试。
我们在前一节中创建的Html.DataGrid() helper就是需要进行单元测试的一个很好的例子。在编写这个helper的过程中可能会犯不少错误。不要相信您写的任何东西!

Here are some expectations for our helper that we might want to test:
• The helper displays the right number of table rows. For example, if you specify that the page size is 2 rows then calling the Html.DataGrid() helper method should render an HTML table that contains 4 rows (1 header row + 2 data rows + 1 pager row).
• The helper selects the right page number. For example, if you specify that the current page index is 1 then page number 2 should be highlighted in bold in the pager user interface.
The test class in Listing 21 contains unit tests for both of these expectations.
对这个helper进行测试,我们有如下一些预期:
• 这个helper会显示正确的表的行数。例如,如果指定每页显示2条记录,那么调用Html.DataGrid() helper 方法应当输出一个4行的HTML table(1表头+2数据行+1表尾).
• 这个helper会选择正确的页码。例如,如果您指定当前页索引值为1,那么页码2在分页用户界面上应当是用粗体突出显示的。
Listing 21中的测试类包含了对上面这些预期的单元测试。
Listing 21 – Helpers\DataGridHelperTests.cs [C#]
1. using System;  
2. using System.Collections.Generic;  
3. using System.Linq;  
4. using System.Text.RegularExpressions;  
5. using Helpers;  
6. using Microsoft.VisualStudio.TestTools.UnitTesting;  
7. using MvcFakes;  
8. using Paging;  
9.  
10. namespace MvcApplication1.Tests.Helpers  
11. {  
12.    [TestClass]  
13.    public class DataGridHelperTests  
14.    {  
15.  
16.        public List CreateItems(int count)  
17.        {  
18.            var items = new List();  
19.            for (var i=0;i < count;i++)  
20.            {  
21.                var newProduct = new Product();  
22.                newProduct.Id = i;  
23.                newProduct.Name = String.Format("Product {0}", i);  
24.                newProduct.Price = count - i;  
25.                items.Add(newProduct);  
26.            }  
27.            return items;  
28.        }  
29.  
30.  
31.        [TestMethod]  
32.        public void SecondPageNumberSelected()  
33.        {  
34.            // Arrange  
35.            var items = CreateItems(5);  
36.            var data = items.AsQueryable().ToPagedList(1, 2);   
37.  
38.            // Act  
39.            var fakeHtmlHelper = new FakeHtmlHelper();  
40.            var results = DataGridHelper.DataGrid(fakeHtmlHelper, data);  
41.  
42.            // Assert  
43.            StringAssert.Contains(results, "2");  
44.          
45.        }  
46.  
47.  
48.        [TestMethod]  
49.        public void CorrectNumberOfRows()  
50.        {  
51.            // Arrange  
52.            var items = CreateItems(5);  
53.            var data = items.AsQueryable().ToPagedList(1, 2);  
54.  
55.            // Act  
56.            var fakeHtmlHelper = new FakeHtmlHelper();  
57.            var results = DataGridHelper.DataGrid(fakeHtmlHelper, data);  
58.  
59.            // Assert (1 header row + 2 data rows + 1 pager row)  
60.            Assert.AreEqual(4, Regex.Matches(results, "