第4章 自定义GridView控件开发

在ASP.NET 3.5 MVC框架中,开发者根据需要,可以创建视图中的用户控件。具体实现方法就是通过在HtmlHelper类中,添加相关的扩展方法而实现。

本章主要实现如何创建一个数据显示GridView控件,其功能包括对选择数据字段的排序、对显示数据字段选项的设置、页码显示,以及编辑、删除等选项的设置。

本章要点:

● 扩展方法概述

● HtmlTextWriter类概述

● GridView控件开发

4.1 扩展方法概述

在实际的应用开发中,开发者一旦完成类、结构或者接口的编译后,在一般情况下,除非重新编译更改后的代码,否则开发者很难在原有代码中添加新的功能。在C# 3.0中,提供了一个扩展方法新特性,可以使得开发者在编译后的程序集中添加相关的方法,而不再需要重新编译原有的代码,或者在不公开源代码的程序集中添加相关的方法,从而很容易地扩展程序原有的功能。

在ASP.NET 3.5 MVC框架中,视图中的用户控件,就是通过在HtmlHelper类中添加相关的扩展方法而实现的。

4.1.1 定义扩展方法

定义一个扩展方法时,首先这个扩展方法需要被定义在一个静态类中,因此这个扩展方法也必须是一个静态方法;其次在扩展方法的参数列表中,需要在第一个参数类型的前面添加关键字this作为修饰符,而紧接其后的就是被扩展类的名称。

代码清单4-1是两个扩展方法的实现代码。

代码清单4-1定义扩展方法

          1: static class MyExtensions
          2: {
          3:  public static void DisplayAssembly(this object obj)
          4:  {
          5:    Console.WriteLine("{0} lives here:{1}", obj.GetType().Name ,
                                    Assembly.GetAssembly (obj.GetType()));
          6:  }
          7:
          8:  public static int ReverseDigits(this int i)
          9:  {
         10:    char[] digits = i.ToString().ToCharArray();
         11:    Array.Reverse(digits);
         12:    string newDigits = new string(digits);
         13:
         14:    return int.Parse(newDigits);
         15: }
         16: }

在上述代码中,定义了一个静态类MyExtensions,其中定义了两个扩展方法。第3行到第6行定义了DisplayAssembly()扩展方法,在该方法的参数列表中,使用了关键字this来修饰object类型,将扩展方法与object类型相关联,因此对于任何object的实例化对象,都可以使用DisplayAssembly()方法,从而扩展了Object的方法,这就是为什么称之为扩展方法的原因。第8行到第15行定义了ReverseDigits()扩展方法,在该方法的参数列表中,使用了关键字this来修饰int类型,将扩展方法与int类型相关联,因此对于任何int类型的数值,都可以使用ReverseDigits()方法,从而扩展了int的方法。

4.1.2 使用扩展方法

使用扩展方法,既可以调用关联类型实例化对象的扩展方法,也可以直接调用静态类中的扩展方法,其中的方法参数输入关联类型。

1.调用关联类型

代码清单4-2演示了调用关联类型实例化对象的扩展方法。

代码清单4-2实例化对象的扩展方法

          1: static void Main(string[] args)
          2: {
          3:  Console.WriteLine("***** Extension Methods *****");
          4:
          5:  int myInt = 12345;
          6:  myInt.DisplayAssembly();
          7:
          8:  System.Data.DataSet d = new System.Data.DataSet();
          9:  d.DisplayAssembly();
          10:
          11: Console.WriteLine("Value of myInt: {0} ", myInt );
          12: Console.WriteLine("Reversed digits of myInt: {0}",
                                  myInt.ReverseDigits() );
          13:
          14: bool b = true;
          15: //b.ReverseDigits()
          16: }

在上述代码中,第5行定义了一个整型数myInt,该整型数的基类是object,因此第6行可以调用object类型的扩展方法DisplayAssembly(),第8行创建一个DataSet类的实例化对象d,该对象d的基类是object,因此也可以调用实例化对象d的扩展方法DisplayAssembly()。在第12行中,myInt是一个整型数,因此可以调用整型数类型的扩展方法ReverseDigits(),而第14行定义的变量是一个布尔值,则不能调用整型数类型的扩展方法ReverseDigits(),如果试图编译第15行语句,将会出现编译错误。

这里需要说明的是,当书写上述代码时,在Visual Studio 2008的集成开发环境中,开发者利用代码智能感知功能,比较容易选择扩展方法。在代码智能感知提示下拉列表框中,扩展方法被标记为一个向下的箭头,如图4-1所示。

active=true

图4-1 扩展方法的代码智能感知

2.调用静态类

代码清单4-3演示了调用静态类的扩展方法。

代码清单4-3调用静态类扩展方法

          1: static void Main(string[] args)
          2: {
          3:  Console.WriteLine("***** Extension Methods *****");
          4:
          5:  int myInt = 12345;
          6:  MyExtensions.DisplayAssembly(myInt);
          7:
          8:  System.Data.DataSet d = new System.Data.DataSet();
          9:  MyExtensions.DisplayAssembly(d);
         10:
         11: Console.WriteLine("Value of myInt: {0} ", myInt );
         12: Console.WriteLine("Reversed digits of myInt: {0}",
                                  MyExtensions.ReverseDigits(myInt) );
         13:
         14: }

在上述代码中,第6行调用了静态类MyExtensions中的扩展方法DisplayAssembly(),该方法关联的类型参数为myInt;第9行也调用了静态类MyExtensions中的扩展方法Display Assembly(),该方法关联的类型参数为d;而第12行中则调用了静态类MyExtensions中的扩展方法ReverseDigits (),该方法关联的类型参数为myInt。

4.1.3 泛型的扩展方法

扩展方法通过关联指定的类型,使得该类型具有扩展的方法,因此也可以针对泛型类型添加扩展方法,这样开发者就可以在指定的泛型类中,使用自己所需要的扩展方法,代码清单4-4是泛型的扩展方法的实现代码。

代码清单4-4泛型的扩展方法

    1: using System;
    2: using System.Collections.Generic;
    3:
    4: namespace ConsoleApplication4_43
    5: {
    6:  public class Customer
    7:  {
    8:    public int CustomerID { get; private set; }
    9:    public string Name { get; set; }
   10:   public string City { get; set; }
   11:
   12:   public Customer(int ID)
   13:   {
   14:    CustomerID = ID;
   15:   }
   16:
   17:   public override string ToString()
   18:   {
   19:    return Name + "\t" + City + "\t" + CustomerID;
   20:   }
   21: }
   22:
   23: public static class Extensions
   24: {
   25:   public static List<T> Append<T>(this List<T> a, List<T> b)
   26:   {
   27:    var newList = new List<T>(a);
   28:    newList.AddRange(b);
   29:    return newList;
   30:   }
   31:
   32:   public static bool Compare(this Customer customer1,
                                    Customer customer2)
   33:   {
   34:    if (customer1.CustomerID == customer2.CustomerID &&
                customer1.Name == customer2.Name &&
                customer1.City == customer2.City)
   35:    {
   36:      return true;
   37:    }
   38:    return false;
   39:   }
   40: }
   41:
   42: class Program
   43: {
   44:   static void Main(string[] args)
   45:   {
   46:     var customers = CreateCustomers();
   47:
   48:     var addedCustomers = new List<Customer>
      {
      new Customer(9)  { Name = "Paolo Accorti", City = "Torino" },
      new Customer(10) { Name = "Diego Roel", City = "Madrid" }
      };
   49:
   50:     var updatedCustomers = customers.Append(addedCustomers);
   51:
   52:     var newCustomer = new Customer(10)
        {
          Name = "Diego Roel", City = "Madrid"
        };
   53:
   54:     foreach (var c in updatedCustomers)
   55:     {
   56:       if (newCustomer.Compare(c))
   57:       {
   58:         Console.WriteLine("The new customer was already in the list");
   59:         return;
   60:       }
   61:     }
   62:     Console.WriteLine("The new customer was not in the list");
   63:   }
   64:
   65:   static List<Customer> CreateCustomers()
   66:   {
   67:     return new List<Customer>
   68:      {
               new Customer(1){Name = "Maria Anders",    City = "Berlin"  },
               new Customer(2){Name = "Laurence Lebihan", City = "Marseille"},
               new Customer(3){Name = "Elizabeth Brown", City = "London"  },
               new Customer(4){Name = "Ann Devon",       City = "London" },
               new Customer(5){Name = "Paolo Accorti",   City = "Torino" }
             };
   69:    }
   70: }
   71:}

在上述代码中,第6行到第21行定义了一个Customer类,第23行到第40行定义了一个静态类Extensions,在该静态类中定义了两个扩展方法。其中第25行到第30行定义了一个泛型列表List<T>类型的扩展方法Append<T>(),该扩展方法主要实现将一个列表添加到原有列表中,并在第27行中使用了关键字var来简化代码的书写;第32行到第39行定义了一个Customer类型的扩展方法Compare(),主要实现Customer类的实例化对象的比较。

在第42行到第70行的客户端代码中,第65行到第69行所定义的CreateCustomers()方法,主要返回一个只包含Customer类的实例化对象的列表。第46行定义了一个隐含类型的变量customers,该变量的类型实际上为一个列表类型(只包含Customer类的实例化对象),可以使用泛型列表List<T>类型的扩展方法Append<T>(),因此第50行调用了这个泛型的扩展方法,将列表addedCustomers与列表customers合并为一个新的列表updatedCustomers。第54行到第61行通过一个循环遍历语句,来读取新列表updatedCustomers中的元素,并在第56行调用扩展方法Compare()来判断newCustomer对象是否存在于新列表updatedCustomers中。

4.2 Html TextWriter类概述

HtmlTextWriter类位于命名空间System.Web.UI之中,该类实现的主要功能是向客户端输出指定的标记字符和文本。在个性化控件的开发中,常常使用HtmlTextWriter类输出指定的Html标记语句。

4.2.1 创建HtmITextWriter类

在使用HtmlTextWriter类时,需要创建HtmlTextWriter类的一个实例,这就需要调用HtmlTextWriter类的构造函数,在该构造函数中,需要输入抽象类System.IO.TextWriter子类的一个实例化对象,如StringWriter类,代码如下:

        HtmlTextWriter writer = new HtmlTextWriter(new StringWriter());

4.2.2 RenderBeginTag()和RenderEndTag()方法

创建了HtmlTextWriter类的实例化对象writer之后,就可以调用HtmlTextWriter类的两个最常用的方法RenderBeginTag()和RenderEndTag()。

在使用RenderBeginTag()时,其中使用了枚举HtmlTextWriterTag,该枚举定义了符合Html 4.0的所有标签,如果需要设置一个Table标签,实现的代码如下:

        writer.RenderBeginTag(HtmlTextWriterTag.Table);

对于每一个RenderBeginTag()方法,必须对应一个RenderEndTag()方法,至于在何时书写所对应的RenderBeginTag()方法,则需要根据HTML的具体语句而定。

例如以下代码:

          writer.RenderBeginTag(HtmlTextWriterTag.Table);
            writer.RenderBeginTag(HtmlTextWriterTag.Tr );
              writer.RenderBeginTag(HtmlTextWriterTag.Th);
                  writer.Write("Customer ID");
              writer.RenderEndTag();
              writer.RenderBeginTag(HtmlTextWriterTag.Th);
                writer.Write("CompanyName");
              writer.RenderEndTag();
            writer.RenderEndTag();
          writer.RenderEndTag();

在上述代码中,构造了一个1行2列的表格,输出的HTML语句,如下所示:

          <table>
            <tr>
                <th>Customer ID</th><th>CompanyName</th>
            </tr>
          </table>

4.2.3 AddAttribute()方法

如果需要在HTML语句中添加指定标签的属性,如在<img>标签中,添加url属性、width属性等,则还需要使用HtmlTextWriter类的AddAttribute()方法。不过这里需要注意的是,AddAttribute语句必须出现在相关的RenderBeginTag的前面。例如,以下代码:

          writer.AddAttribute("url", "../pic.png");
          writer.AddAttribute("width", "50");
          writer.AddAttribute("height", "100");
          writer.RenderBeginTag(HtmlTextWriterTag.Img);
          writer.RenderEndTag();

上述代码所实现的HTML语句,如下所示:

        <img url="../pic.png" width="50" height="100" />

4.3 GridView控件开发

4.3.1 GridView控件的基本功能

数据显示控件——GridView的运行界面,如图4-2所示。

active=true

图4-2 GridView控件的运行界面

从图中可以看出,GridView控件具有分页、排序功能,单击GridView控件下方的分页链接,可以显示指定页面的数据。在分页功能中,最左边是“前一页”链接,最右边则是“后一页”链接,并显示最前面的两个页面及最后面的两个页面,页面的导航功能比较强大。

单击GridView控件上方相关的字段链接,可以对该字段实现排序,如图4-3所示。

active=true

图4-3 GridView控件的排序

图4-3是对字段“City”实现排序后的结果,在GridView控件的最右边,还显示了每条记录所对应的“Edit”链接和“Delete”链接,单击“Edit”链接,就会转到相关的数据编辑页面;单击“Delete”链接,就会删除指定的数据。

4.3.2 构建分页列表

为了对需要显示的数据,构建功能较为强大的分页功能,这里构建了一个泛型的分页列表——PagedList<T>类,用于表示分页的基本数据,PagedList<T>类的UML类图如图4-4所示。

active=true

图4-4 PagedList<T>的UML类图

从图4-4中可以看出,为实现分页功能,这里定义了分页的几个基本属性,它们分别是页面索引PageIndex(表示当前页面)、一个页面中所显示的数据记录数量PageSize、数据的总记录数量TotalItemCount及总的页面数量TotalPageCount,还定义了SortExpression属性,用于表示需要排序的字符串,IdentityColumnName属性主要表示数据表中的主键字段的名称,以便实现相关的编辑、删除等操作。

PagedList<T>类的实现代码,见代码清单4-5。

代码清单4-5 PagedList<T>的实现代码

          1: public class PagedList<T> : List<T>
          2: {
          3:  public PagedList(IEnumerable<T> items, int pageIndex, int pageSize,
                                int totalItemCount, string identityColumnName,
                                string sortExpression)
          4:  {
          5:    this.AddRange(items);
          6:    this.PageIndex = pageIndex;
          7:    this.PageSize = pageSize;
          8:    this.SortExpression = sortExpression;
          9:    this.TotalItemCount = totalItemCount;
         10:    this.TotalPageCount = (int)Math.Ceiling(
                                          totalItemCount / (double)pageSize);
         11:    this.IdentityColumnName = identityColumnName;
         12:  }
         13:
         14:  public int PageIndex { get; set; }
         15:  public int PageSize { get; set; }
         16:  public string SortExpression { get; set; }
         17:  public int TotalItemCount { get; set; }
         18:  public int TotalPageCount { get; private set; }
         19:  public string IdentityColumnName { get; set; }
         20: }

上述代码的实现较为简单,第14行到第19行分别定义了6个属性的读写器,这是C# 3.0中读写器的写法。

代码第3行到第12行定义了PagedList<T>类的构造函数,主要实现列表中元素的添加,以及6个属性的初始化。

4.3.3 构建LINQ查询的扩展方法

在ASP.NET 3.5中,开发者可以使用LINQ关键技术,非常方便地实现各种数据的查询,为实现将LINQ的数据查询结果显示在GridView控件中,可以采用构建LINQ查询的扩展方法,这里构建了一个PageLinqExtensions类,PageLinqExtensions类的UML类图如图4-5所示。

active=true

图4-5 PageLinqExtensions的UML类图

从图中可以看出,针对LINQ查询及List类型数据实现了3个重载的扩展方法,通过设置页面索引PageIndex、页面大小PageSize、排序字符串SortExpression及主键字段名称IdentityColumnName,使得LINQ的查询数据(IQueryable)或者List类型的数据是分页列表PagedList<T>类的实例化对象,具有分页功能。

PageLinqExtensions类的实现代码,见代码清单4-6。

代码清单4-6 PageLinqExtensions的实现代码

          1: public static class PageLinqExtensions
          2: {
          3:  public static PagedList<T> ToPagedList<T>(
                      this IQueryable<T> allItems,
                      int? pageIndex,
                      int pageSize)
          4:  {
          5:    return ToPagedList<T>(allItems, pageIndex, pageSize, null,
                                      String.Empty);
          6:  }
          7:
          8:  public static PagedList<T> ToPagedList<T>(
                      this IQueryable<T> allItems,
                      int? pageIndex,
                      int pageSize,
                      string identityColumnName)
          9:  {
         10:   return ToPagedList<T>(allItems, pageIndex, pageSize,
                                      identityColumnName, String.Empty);
         11:  }
         12:
         13:  public static PagedList<T> ToPagedList<T>(
                      this IQueryable<T> allItems,
                      int? pageIndex,
                      int pageSize,
                      string identityColumnName,
                      string sort)
         14:  {
         15:    var truePageIndex = pageIndex ? ? 0;
         16:    var itemIndex = truePageIndex * pageSize;
         17:    var pageOfItems = allItems.Skip(itemIndex).Take(pageSize);
         18:    var totalItemCount = allItems.Count();
         19:
         20:    return new PagedList<T>(pageOfItems, truePageIndex, pageSize,
                                  totalItemCount, identityColumnName, sort);
         21:  }
         22:
         23: }

在上述代码中,对LINQ的查询数据IQueryable<T>对象,定义了3个重载的扩展方法ToPagedList<T>,它们分别是第3行到第6行的扩展方法,其中有输入当前页面pageIndex和页面大小pageSize;第8行到第11行的扩展方法,其中有输入当前页面pageIndex、页面大小pageSize及主键字段名称identityColumnName;第13行到第21行的扩展方法,其中有输入当前页面pageIndex、页面大小pageSize、主键字段名称identityColumnName及排序的字符串。

通过定义PageLinqExtensions类,使得LINQ的查询数据IQueryable<T>对象可以使用ToPagedList<T>()方法,使得LINQ的查询数据具有分页、排序功能。

4.3.4 构建GridViewOption类

为了让GridView控件具有编辑、删除链接,这里专门创建了一个GridViewOption类,其UML类图如图4-6所示。

active=true

图4-6 GridViewOption的UML类图

在GridViewOption类中,定义了表头的显示文字Columns,编辑、删除链接的相关属性,例如,是否显示编辑链接ShowEditButton、编辑链接的文字EditButtonText及编辑功能的动作方法EditAction等。

GridViewOption类的实现代码,见代码清单4-7。

代码清单4-7 GridViewOption的实现代码

          1: public class GridViewOption
          2: {
          3:  private bool showEditButton = true;
          4:  private bool showDeleteButton = true;
          5:
          6:  private string editButtonText = "Edit";
          7:  private string deleteButtonText = "Delete";
          8:
          9:  private string editAction = "Edit";
         10:  private string deleteAction = "Delete";
         11:
         12:  public bool ShowEditButton
         13:  {
         14:    get { return showEditButton; }
         15:    set { showEditButton = value; }
         16:  }
         17:
         18:  public bool ShowDeleteButton
         19:  {
         20:    get { return showDeleteButton; }
         21:    set { showDeleteButton = value; }
         22:  }
         23:
         24:  public string EditButtonText
         25:  {
         26:    get { return editButtonText; }
         27:    set { editButtonText = value; }
         28:  }
         29:
         30:  public string DeleteButtonText
         31:  {
         32:    get { return deleteButtonText; }
         33:    set { deleteButtonText = value; }
         34:  }
         35:
         36:  public string EditAction
         37:  {
         38:    get { return editAction; }
         39:    set { editAction = value; }
         40:  }
         41:
         42:  public string DeleteAction
         43:  {
         44:    get { return deleteAction; }
         45:    set { deleteAction = value; }
         46:  }
         47:
         48:  private string[] columns;
         49:
         50:  public string[] Columns
         51:  {
         52:    get { return columns ; }
         53:    set { columns= value; }
         54:  }
         55: }

上述的代码实现也比较简单,主要定义了7个基本属性的读写器,而且这7个属性还具有默认值。也就是说,在默认情况下,是显示“Edit”、“Delete”链接的,它们的动作方法名称分别为Edit和Delete。

开发者如果需要改变这些默认值,只需要重新设置GridViewOption类中的相关属性即可。

4.3.5 构建GridViewHeIper类

在实现了LINQ查询数据的分页、排序功能,定义了编辑、删除链接的相关属性之后,此时就需要专门实现GridView控件,也就是说,需要对HtmlHelper类实现相关的扩展方法,从而实现GridView控件的输出。

这里专门定义了一个GridViewHelper类,它的UML类图如图4-7所示。

active=true

图4-7 GridViewHelper的UML类图

在GridViewHelper类中,定义了3个重载的GridView<T>扩展方法,通过设置模型数据、显示字段,以及编辑、删除链接等参数,即可显示功能较为强大的GridView数据结果。

GridViewHelper类的实现代码,见代码清单4-8。

代码清单4-8 GridViewHelper的实现代码

     1: public static class GridViewHelper
     2: {
     3:  public static string GridView<T>(this HtmlHelper helper)
     4:  {
     5:    return GridView<T>(helper, null, null, new GridViewOption());
     6:  }
     7:
     8:  public static string GridView<T>(this HtmlHelper helper, object data)
     9:  {
    10:    return GridView<T>(helper, data, null, new GridViewOption());
    11:  }
    12:
    13:  public static string GridView<T>(this HtmlHelper helper, object data,
       string[] columns, GridViewOption options)
    14:  {
    15:    var items = (IEnumerable<T>)data;
    16:    if (items == null)
    17:      items = (IEnumerable<T>)helper.ViewData.Model;
    18:
    19:    if (columns == null)
    20:      columns = typeof(T).GetProperties().Select(p => p.Name)
        .ToArray();
    21:
    22:    var writer = new HtmlTextWriter(new StringWriter());
    23:    writer.RenderBeginTag(HtmlTextWriterTag.Table);
    24:
    25:    writer.RenderBeginTag(HtmlTextWriterTag.Thead);
    26:    RenderHeader(helper, writer, columns, options);
    27:    writer.RenderEndTag();
    28:
    29:    string identityColumnName= ((PagedList<T>)items)
                                        .IdentityColumnName ;
    30:
    31:    writer.RenderBeginTag(HtmlTextWriterTag.Tbody);
    32:    foreach (var item in items)
    33:      RenderRow<T>(helper, writer, columns, item, identityColumnName,
                            options);
    34:    writer.RenderEndTag();
    35:
    36:    RenderPagerRow<T>(helper, writer, (PagedList<T>)items,
                            columns.Count());
    37:
    38:    writer.RenderEndTag();
    39:
    40:    return writer.InnerWriter.ToString();
    41:  }
    42:
    43:  private static void RenderHeader(HtmlHelper helper,
              HtmlTextWriter writer, string[] columns, GridViewOption options)
    44:  {
    45:    writer.RenderBeginTag(HtmlTextWriterTag.Tr);
    46:    foreach (var columnName in columns)
    47:    {
    48:      writer.RenderBeginTag(HtmlTextWriterTag.Th);
    49:      var currentAction = (string)helper.ViewContext
                                            .RouteData.Values["action"];
    50:      string link=null;
    51:      if (options.Columns == null)
    52:       link = helper.ActionLink(columnName, currentAction,
                                        new { sort = columnName });
    53:      else {
    54:        link = helper.ActionLink(options.Columns[i], currentAction,
                        new { sort = columnName });
    55:         i++; }
    56:      writer.Write(link);
    57:      writer.RenderEndTag(); }
    58:    bool showEditColumn = options.ShowEditButton ——
                                  options.ShowDeleteButton;
    59:    if (showEditColumn){
    60:      writer.RenderBeginTag(HtmlTextWriterTag.Th);
    61:      writer.Write(helper.Encode(""));
    62:      writer.RenderEndTag(); }
    63:    writer.RenderEndTag();
    64:  }
    65:
    66:  private static void RenderRow<T>(HtmlHelper helper,
              HtmlTextWriter writer, string[] columns, T item ,
              string identityColumnName, GridViewOption options)
    67:  {
    68:    writer.RenderBeginTag(HtmlTextWriterTag.Tr);
    69:    foreach (var columnName in columns)
    70:    {
    71:      writer.RenderBeginTag(HtmlTextWriterTag.Td);
    72:      var value = typeof(T).GetProperty(columnName)
                                .GetValue(item, null) ? ? String.Empty;
    73:      writer.Write(helper.Encode(value.ToString()));
    74:      writer.RenderEndTag();
    75:      }
    76:
    77:    bool showEditColumn = options.ShowEditButton ——
                                  options.ShowDeleteButton;
    78:
    79:    if (showEditColumn)
    80:    {
    81:      var identityVaule = typeof(T).GetProperty(identityColumnName)
                                        .GetValue(item, null);
    82:      writer.RenderBeginTag(HtmlTextWriterTag.Td);
    83:
    84:      if ( options.ShowEditButton)
    85:      {
    86:       var link = helper.ActionLink(options.EditButtonText,
                          options.EditAction , new { id =identityVaule });
    87:       writer.Write(link);
    88:       writer.Write(" ");
    89:      }
    90:
    91:      if (options.ShowDeleteButton )
    92:      {
    93:       var link = helper.ActionLink(options.DeleteButtonText,
                        options.DeleteAction , new { id = identityVaule });
    94:       writer.Write(link);
    95:      }
    96:      writer.RenderEndTag();
    97:    }
    98:    writer.RenderEndTag();
    99:  }
    100:
    101:  private static void RenderPagerRow<T>(HtmlHelper helper,
              HtmlTextWriter writer, PagedList<T> items, int columnCount)
    102:  {
    103:    int nrOfPagesToDisplay = 10;
    104:
    105:    if (items.TotalPageCount == 1)
    106:     return;
    107:
    108:    writer.RenderBeginTag(HtmlTextWriterTag.Tr);
    109:    writer.AddAttribute(HtmlTextWriterAttribute.Colspan,
                                  columnCount.ToString());
    110:    writer.RenderBeginTag(HtmlTextWriterTag.Td);
    111:    var currentAction = (string)helper.ViewContext.RouteData
                                            .Values["action"];
    112:
    113:    if (items.PageIndex >= 1)
    114:    {
    115:     var linkText = String.Format("{0}", "<<<");
    116:     var link = helper.ActionLink(linkText, currentAction,
                new { page = items.PageIndex, sort = items.SortExpression });
    117:     writer.Write(link + "&nbsp; ");
    118:    }
    119:
    120:    int start = 0;
    121:    int end = items.TotalPageCount;
    122:
    123:    if (items.TotalPageCount > nrOfPagesToDisplay)
    124:    {
    125:     int middle = (int)Math.Ceiling(nrOfPagesToDisplay / 2d) -1;
    126:     int below = (items.PageIndex - middle);
    127:     int above = (items.PageIndex + middle);
    128:
    129:     if (below < 4)
    130:     {
    131:      above = nrOfPagesToDisplay;
    132:      below = 0;
    133:     }
    134:     else if (above > (items.TotalPageCount -4))
    135:     {
    136:      above = items.TotalPageCount;
    137:      below = (items.TotalPageCount - nrOfPagesToDisplay);
    138:     }
    139:
    140:     start = below;
    141:     end = above;
    142:    }
    143:
    144:    if (start > 3)
    145:    {
    146:     var linkText = String.Format("{0}", "1");
    147:     var link = helper.ActionLink(linkText, currentAction,
                        new { page = 1, sort = items.SortExpression });
    148:     writer.Write(link + "&nbsp; ");
    149:
    150:     linkText = String.Format("{0}", "2");
    151:     link = helper.ActionLink(linkText, currentAction,
                      new { page = 2, sort = items.SortExpression });
    152:     writer.Write(link + "&nbsp; ");
    153:     writer.Write(String.Format("{0}", "..."));
    154:    }
    155:
    156:    for (var i = start; i < end; i++)
    157:    {
    158:     if (i == items.PageIndex)
    159:     {
    160:      writer.Write(String.Format("<strong>{0}</strong>&nbsp; ",
                                i + 1));
    161:     }
    162:     else
    163:     {
    164:      var linkText = String.Format("{0}", i + 1);
    165:      var link = helper.ActionLink(linkText, currentAction,
                          new { page = i + 1, sort = items.SortExpression });
    166:      writer.Write(link + "&nbsp; ");
    167:     }
    168:    }
    169:
    170:    if (end < (items.TotalPageCount -3))
    171:    {
    172:     writer.Write(String.Format("{0}", "..."));
    173:     var linkText = String.Format("{0}", items.TotalPageCount -1);
    174:     var link = helper.ActionLink(linkText, currentAction,
                          new { page = items.TotalPageCount -1,
                                sort = items.SortExpression });
    175:     writer.Write(link + "&nbsp; ");
    176:
    177:     linkText = String.Format("{0}", items.TotalPageCount);
    178:     link = helper.ActionLink(linkText, currentAction,
                      new { page = items.TotalPageCount,
                            sort = items.SortExpression });
    179:     writer.Write(link + "&nbsp; ");
    180:    }
    181:
    182:    if (items.PageIndex + 2 <= items.TotalPageCount)
    183:    {
    184:     var linkText = String.Format("{0}", ">>>");
    185:     var link = helper.ActionLink(linkText, currentAction,
                        new { page = items.PageIndex + 2,
                                sort = items.SortExpression });
    186:     writer.Write(link + "&nbsp; ");
    187:    }
    188:
    189:    writer.RenderEndTag();
    190:    writer.RenderEndTag();
    191:  }
    192: }

上述的代码比较长,不过可以分为4个部分,它们分别是第1部分的第3行到第41行、第2部分的第43行到第64行、第3部分的第66行到第99行及第4部分的第101行到第191行。

第1部分是代码的主要部分,其中定义了3个重载的GridView<T>扩展方法。在第3行到第6行的扩展方法中,不需要输入任何参数;在第8行到第11行的扩展方法中,需要输入模型数据参数;而在第13行到第41行的扩展方法中,则需要输入模型数据、显示数据字段,以及编辑、删除等相关设置。

在扩展方法中,如果没有输入模型数据,代码第17行会通过HtmlHelper类,自动获取视图ViewData中的模型数据Model;如果没有输入需要显示的数据字段,代码第20行会自动获得指定数据表的所有字段名称。

代码第22行创建HtmlTextWriter类的一个实例化对象writer,以便构建显示数据表格的HTML语句,其中第26行构建数据表的表头,第29行获得数据表中的主键字段,而代码第33行则实现数据显示的HTML语句,第36行代码构建分页界面的HTML语句。

第2部分代码主要实现数据表的表头。第45行构建表格中的多行标记,第48行则构建表格中的多列标记。第51行到第57行,用于设置表头的字段;第59行到第63行,则根据用户对编辑、删除的相关设置,判断是否显示相对应的空白表头。

第3部分代码主要实现数据表的数据显示。其中第68行到第75行显示数据表的数据,第79行到第97行用于显示数据的编辑、删除链接。

第4部分代码主要实现数据页码的显示。其中第103行设置页码显示的个数为10,第105行判断总的页码数量,如果仅为一个页面,则在106行直接退出。第113行到第118行显示最左边的页码显示部分“<<<”;第123行到第138行计算需要显示页码的开始和结束部分;第144行到第154行显示左边的页码省略部分“…”;第156行到第168行显示中间部分的页码;第170行到第180行显示右边的页码省略部分“…”;第182行到第187行显示最右边的页码显示部分“>>>”。

4.3.6 LINQ动态查询

LINQ是ASP.NET 3.5所提供的一种新的查询语言,开发者可以利用LINQ非常方便地构建类型安全的查询语句,例如如下语句:

        var model = (from c in db.Customers select c).OrderBy(c=>c.CompanyName);

针对数据表Customers查询所有的字段,并且按照CompanyName字段排序,在实现查询的排序功能时,这里采用了Lambda表达式,实际上是一种静态的排序,也就是说,不能实现参数化的字段排序,即LINQ动态查询。

微软公司的LINQ开发团队,为了让开发者实现LINQ动态查询,即提供字符串形式的参数,专门开发了实现LINQ动态查询的类库——DynamicLibrary。

开发者可以免费使用这个类库DynamicLibrary,轻松实现LINQ动态查询,代码如下:

        var model = (from c in db.Customers select c).OrderBy(sort);

在上述代码中,排序字段中输入了字符串参数sort变量,实现了LINQ的参数化,即动态排序。

关于类库DynamicLibrary的其他使用方法,请参照相关的文档说明。

4.3.7 GridView的使用方法

在使用GridView控件时,需要在控制器中设置分页、排序参数,在视图中设置编辑、删除等链接选项,以下分别予以说明。

1.控制器中设置分页、排序参数

GridView中分页、排序参数的设置,是在控制器相关的方法中实现的,例如如下代码:

          var model1 = (from c in db.Customers select c).OrderBy(sort)
                        .ToPagedList(page, 5, "CustomerID", sort);

在上述代码中,排序字段中输入了字符串参数sort变量,实现了LINQ的动态排序,然后通过ToPagedList()扩展方法,设置了当前页面page、页面显示数为5、数据表主键字段“CustomerID”及排序参数sort。

2.视图中设置编辑、删除选项

在视图页面中,代码设置如下:

          <% =Html.GridView<Customers>(Model, new string[] { "CustomerID",
                          "CompanyName", "ContactName", "Address", "City" }),
                          new GridViewOption()  %>

在上述代码中,由于显示的是数据表Customers中的数据,因此GridView控件的类型为Customers,其中传入了两个参数,它们分别是模型数据Model和需要显示的数据字段,上述代码的运行界面如图4-8所示。

active=true

图4-8 GridView控件的运行界面

如果需要修改编辑、删除链接的文字显示等,可以设置如下的代码:

          <% =Html.GridView<Customers>(Model, new string[] { "CustomerID",
                    "CompanyName", "ContactName", "Address", "City" },
            new GridViewOption() { EditButtonText="编辑", DeleteButtonText ="删除" })
          %>

上述代码设置了编辑、删除链接的文字分别为“编辑”和“删除”,其运行界面如图4-9所示。

active=true

图4-9 GridView控件的编辑、删除链接设置

如果需要修改表格头部字段的文字显示等,可以设置如下的代码:

          <% =Html.GridView<Customers>(Model,
                    new string[] { "CustomerID", "CompanyName", "ContactName",
                                  "Address", "City" },
                    new GridViewOption() { EditButtonText = "编辑",
                          DeleteButtonText = "删除",
                Columns =new string[] {"编号" , "公司名称", "联系人", "地址", "城市"} })
          %>

在上述代码中,通过GridViewOption类中的Columns属性设置了表格头部的文字分别为“编号”、“公司名称”、“联系人”、“地址”和“城市”,其运行界面如图4-10所示。

active=true

图4-10 GridView控件的表格头部设置

这里需要说明的是,Columns属性要与其前面所设置的显示字段一致。

4.4 思考与提高

本章通过对HtmlHelper类添加扩展方法,实现了一个数据显示控件——GridView,其基本功能包括按照字段排序、页码显示、表头文字修改,以及编辑、删除等链接显示,满足一般情况下开发者的需求。

请读者根据自己的喜好,设计表格的不同样式,使得GridView控件具有设置不同显示风格样式的功能。