本系列是讲解如何在asp.net mvc中对数据进行展示、排序、分页等的系列文章。在上周的文章中,一步一步教会了大家如何使用ASP.NET MVC框架去的展示数据。在上周的文章中,我们先用Visual Studio创建了一个新的ASP.NET MVC应用程序,接着连接到了Northwind数据库,并展示了如何使用微软的LINQ-SQL的工具去访问数据库中的数据,接着指导如何去实现视图层去展示产品信息及如何设计控制器。
本文是在上一篇文章的例子基础上,展示了如何去实现数据的双向排序。如果你是已经熟悉ASP.NET WebForm开发的开发者,你应该知道,在GridView控件中可以很简单的通过点击就能实现排序。但可惜的是,在ASP.NET MVC中实现排序的功能并不是那么简单,但工作量也没有显着增多。在ASP.NET MVC中,我们能更多地控制网格和排序的界面布局和标记,以及通过何种机制来实现排序。以往使用GridView控件时,排序是通过将参数以POSTBACK的形式回传到后台,以决定用什么样的列进行排序以及是以升序或降序-排列,回传的参数作为隐藏表单域提交。在本文中,我们将使用查询字符串参数来指定排序参数,这意味着排序的顺序可以被搜索引擎搜索到,能通过电子邮件发送给同事,还能做很多GridView内置排序功能不能实现的事情。
与上一篇文章一样,本文提供了分步的指导说明,包括一个完整的可以工作的代码例子,在文章末尾可以下载。
步骤0:一个简要指南
本文将介绍如何实现双向的排序,并假定读者已经阅读掌握了上一篇文章介绍的内容。
在上一篇文章中,我们可以通过ASP.NET MVC中的URL转发功能,以下面的地址形式访问产品的首页:
www.yoursite.com/Products/Index(可以简写成www.yoursite.com/Products)。本文中,我们将用下面的URL去访问要排序的内容:
www.yoursite.com/Products/Sortable?sortBy=ColumnNameascending=true|false。
具体的一些例子如下:
/Products/Sortable- 这表示按默认顺序排列产品。当没指定排序的列时,默认按产品名称的字母顺序(如升序)排列。换句话说,如果SortBy参数没有提供,按产品名称排序,如果不提供ascending参数,按递增顺序排列。
/Products/Sortable?sortBy=UnitPrice 按单价的升序排序产品。
/Products/Sortable?sortBy=UnitPriceascending=false – 按UnitPrice列降序排序(即从最昂贵的到价格最便宜的)。
跟使用GridView控件一样,在点击网格中标题行中的列名时可以进行。但不同于GridView的是,我们每次点列的这些标题,是以超链接的形式实现的,并且带有参数,比
如表格中有价格这个列,当第一次点击列名时,将以
www.yoursite.com/Products/Sortable?sortBy=UnitPriceascending=true的形式发送链接到后端,请注意的是,在网格中显示的列名,不一定跟在URL中sortBy查询字符串参数中传递的名称是一样的。sortBy 参数提供的是在数据库中的列名,两者并不要求一定相同。
步骤1:创建指定的视图模型
在上一篇文章的演示中,我们使用了产品的集合作为其实体模型(以NorthwindDataContext去命名)。这对简单的网格来说是可以的,但对于要排序的数据表格,需要知道一点的不仅仅是产品的集合,还要视图层方面知道哪些列的数据需要进行排序,是按升序或降序排序,如果用户要点的列已经按某一个顺序已排序的话,则此时会按原来的顺序排序(假设某列已经是按升序排列,用户点标题一次,则倒过来按降序排列,再点一次,又按升序,如此类推)。
为此,我们添加一个新类,这些类被称为特定视图服务的模型,打开上一篇文章中已经实现的应用程序,在Models文件夹下添加一个名为ProductGridModel.cs 的文件,代码如下:
namespace Web.Models
{
public class ProductGridModel
{
// Data properties
public IEnumerable Product Products { get ; set ; }
// Sorting-related properties
public string SortBy { get ; set上海徐汇企业网站制作n style="color: #000000;">; }
public bool SortAscending { get ; set ; }
public string SortExpression
{
get
{
return this .SortAscending ?上海企业网站制作pan> this.SortBy + " asc" : this.SortBy + " desc";
}
}
}
}
该ProductGridModel类定义了一个产品属性,它是一个集合类的属性,用来显示产品集合,同时也有三种排序相关的属性:
SortBy
在数据库中用来排序的数据列名称
SortAscending
一个布尔值,指示是否用升序排序数据
SortExpression
只读属性,返回一个排序字符串,其构造为SortBy和SortAscending值的组合。
例如,如果SortBy分为UnitePrice和SortAscending是true,SortExpression的值为
UnitedPrice asc。
如果SortBy设置为Discontinued 和SortAscending是false,则SortExpression返回Discontinued desc 。
步骤2:创建Sortable 的Action方法
在上一篇教程中,我们创建了一个名为ProductsController的控制器,其中有一个叫index的action和一个辅助属性DataContext,本文中,我们将添加一个新的action方法到控制器中,并命名为Sortable,当有来自如www.yoursite.com/Products/Sortable的请求时,则执行该排序方法。
ASP.NET MVC中实现了自动参数绑定,来自URL或其他的参数请求,将被映射到执行的实际的action中去。例如,如果你在控制器action中定义了一个输入参数,名为sortBy,则MVC框架将搜索传入的请求的参数,看是否有任何具有相同名称的参数(在这里,一个参数可能是一个提交表单域,一个查询字符串参数或路由参数。)如果找到一个匹配,则自动把参数的值得赋给action中定义的参数。
下面是其实际代码:
public class ProductsController : Controller
{
...
// GET: /Products/Sortable?SortColumn=columnNameAscending=true|false
public ActionResult Sortable( string sortBy = " ProductName " , bool ascending = true )
{
var model
= new ProductGridModel()
{
SortBy
= sortBy,
SortAscending
= ascending
};
model.Products
= this .DataContext.Products.OrderBy(model.SortExpression);
return View(model);
}
}
请注意,action中接受两个输入参数:sortBy和ascending。任何来自URL的请求,只要符合这两个参数的名称的,其值得都会被自动匹配传入到该action中去, 也就是说,如果有人访问www.yoursite.com不指定查询字符串参数,则默认按照产品的名称进行升序排序。
Sortable的action首先创建一个新的ProductGridModel实例,命名为model,并且对model的SortBy和SortAscending属性进行赋值,接着,Sortable action获得了产品的集合(this.DataContext.Products),并调用其中的OrderBy方法进行排序,而排序的参数表达式正好是SortExpression。最后,将model模型返回给一个强类型的视图,。
如果您熟悉使用LINQ,你会觉得我在这里使用的OrderBy方法有点奇怪,你可能会用LINQ中的lambda表达式如下这样写,如:this.DataContext.Products.OrderBy(p =p.ProductName)。
OrderBy方法,我使用的是不标准的LINQ,相反是微软的动态LINQ库中的方法
(http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx),它是一个库允许使用字符串做为查询参数。本文附件中提供了一个名为Dynamic.cs的文件(它在HelperClasses文件夹中),其中包含了OrderBy扩展方法签名,大家可以去学习一下。
步骤3:创建视图
鼠标右键点控制器中的Sortable action,在弹出的菜单中选择添加视图选项。从添加视图对话框中,选中Create a strongly-typed view 复选框,然后从View data class 下拉选择Web.Models.ProductGridModel,点击确定。这时会创建视图Sortable.aspx,如下图:
接下来,修改代码如下:
table class ="grid"
tr
th Product / th
th Category / th
th Qty/Unit / th
上海闵行企业网站制作an style="color: #800000;">th Price / th
th Discontinued / th
/ tr % foreach (var item in Model.Products) { %
tr
td class ="left" % : item.ProductName % / td
td class ="left" % : item.Category.CategoryName % / td
td class ="left" % : item.QuantityPerUnit % / td
td class ="right" % : String .Format( " {0:C} " , item.UnitPrice) % / td
td
% if (item.Discontinued) { %
img src ="%=Url.Content(" ~/Content/cancel.png") % " alt="Discontinued" title="Discontinued" /
% } %
/ td
/ tr
% } % / table
上面的代码跟上一篇文章中你看到的基本没什么区别,唯一的区别,其实是在foreach语句中,在上一篇文章中,是:
▲
foreach(var item in Model) { ... },而本文中,其模型变成了ProductGridModel实例,其产品属性包含产品的集合。因此,在foreach循环遍历Model.Products。运行后,你会发现访问页面时默认的是按产品的升序排列的:
现在网格的列标题是文本,等下我们会将其修改为超级链接,但现在我们也可以马上在浏览器中,通过输入的方法体验下了,举例来说,如果你输入:
www.yoursite.com/Products/Sortable?sortBy=UnitPriceascending=false- 你应该看到按 照价格从高到低排列的产品。
在网格中添加排序链接
此时,用户无法直观地去排序,除非他在浏览器中象上面的方法输入查询字符串,因此我们在每个网格的标题行中提供超链接指向适当的网址。乍一看,这可能会像一个简单的任务,比如产品名称列,其url应该是
Products/Sortable?sortBy=ProductNameascending=true,而价格列,则应该是
Products/Sortable?sortBy=UnitPriceascending=true,如此类推,归纳一下,其URL的连接方式应该象:
Products/Sortable?sortBy=ColumnNameascending=false,同时,还应该增加一个象图标这样的指示,让用户可以清楚地看到当前排序的情况。
要做到这一点,需要创建一个局部视图。一个局部视图看上去跟WebForms模型中的用户控件的概念差不多。总之,这是一可以重用的视图。
在解决方案资源管理器中的Views/Shared目录下,鼠标右键点击,在弹出的菜单中选择添加视图,将其命名为SmartLink,并选中下面的两个复选框,然后选择Web.Models.ProductGridModel作为其view data class,如下图。
单击确定后,Visual Studio将创建一个新的局部视图(SmartLink.ascx)。其中局部视图只包含下列标记:
% @Control
Language = " C# " Inherits = " System.Web.Mvc.ViewUserControlWeb.Models.ProductGridModel " %
我们现在来学习如何将局部视图添加到正常的视图中去,这可以使用下列Html.RenderParial方法之一:
% Html.RenderPartial( " partialViewName " ); %
% Html.RenderPartial( " partialViewName " , viewData); %
% Html.RenderPartial( " partialViewName " , model); %
% Html.RenderPartial( " partialViewName " , model, viewData); %
可选的参数是一个ViewDataDictionary的ViewData对象。如果提供,可以通过
ViewData["name"].去访问这些值。
当要展示局部视图的时候,我们还需要将ProductGridModel和一些额外的信息传进来,下面的代码展示了对于产品名称一列这个表头,我们是如何通过局部视图去产生的,注意的是,我们通过ViewDataDictionary可以设置表头中显示的列的名称以及该列实际对应的是数据库中的列名:
% Html.RenderPartial( " SmartLink " , Model, new ViewDataDictionary {
{ " ColumnName " , " ProductName " }, { " DisplayName " , " Product " } }); %
这里设置了列名是ProductName,而显示在表头中的列名是Product。同理,其他表头列也是这样的处理,就可以把Model传递到部分视图中来了。
table class ="grid"
tr
th % Html.RenderPartial( " SmartLink " , Model, new ViewDataDictionary { { " ColumnName " , " ProductName " }, { " DisplayName " , " Product " } }); % / th
th % Html.RenderPartial( " SmartLink " , Model, new ViewDataDictionary { { " ColumnName " , " Category.CategoryName " }, { " DisplayName " , " Category " } }); % / th
th % Html.RenderPartial( " SmartLink " , Model, new ViewDataDictionary { { " ColumnName " , " QuantityPerUnit " }, { " DisplayName " , " Qty/Unit " } }); % / th
th % Html.RenderPartial( " SmartLink " , Model, new ViewDataDictionary { { " ColumnName " , " UnitPrice " }, { " DisplayName " , " Price " } }); % / th
th % Html.RenderPartial( " SmartLink " , Model, new ViewDataDictionary { { " ColumnName " , " Discontinued " }, { " DisplayName " , " Discontinued " } }); % / th
/ tr
...
/ table
现在我们可以看到,从局部视图中可以通过Model属性访问在Sortable视图中的任何值了,访问的方法是通过ViewData[name]即可。还要记得,我们之所以要在这里使用局部视图,其目的为某个特定的列生成超级链接。
我们接下来要判断某个列是否要升序还是降序排列,下面的代码创建了isDescending变量,这个值只有当要排序的列(由Model.SortBy产生)和数据库中的列名相同(由ViewDate[ColumnName]产生)时,并且当前的排序为升序(Model.SortAscending)时,其值才为True。
var isDescending = string .CompareOrdinal(Model.SortBy,
ViewData[ " ColumnName " ].ToString()) == 0 Model.SortAscending;
同时,我们最好通过设置CSS样式,告诉用户当前的排序方向,因此我们在 CustomStyles.css的CSS类定义了两个CSS类-sortAsc和sortDesc,增加了一个向上或向下箭头,可以用如下代码去判断:
f ( string .CompareOrdinal(Model.SortBy, ViewData[ " ColumnName " ].ToString()) == 0 )
{
if (Model.SortAscending)
htmlAttributes.Add( " class " , " sortAsc " );
else
htmlAttributes.Add( " class " , " sortDesc " );
最后,完成的局部视图代码如下:
%
var isDescending = string .CompareOrdinal(Model.SortBy, ViewData[ " ColumnName " ].ToString()) == 0 Model.SortAscending;
var routeData
= new RouteValueDictionary { { " sortBy " , ViewData[ " ColumnName " ].ToString() }, { " ascending " , ! isDescending } };
var htmlAttributes
= new Dictionary string , object ();
if ( string .CompareOrdinal(Model.SortBy, ViewData[ " ColumnName " ].ToString()) == 0 )
{
if (Model.SortAscending)
htmlAttributes.Add( " class " , " sortAsc " );
else
htmlAttributes.Add( " class " , " sortDesc " );
}
%% : Html.ActionLink(
ViewData[ " DisplayName " ].ToString(), // Link Text
Html.ViewContext.RouteData.Values[ " action " ].ToString(), // Action
Html.ViewContext.RouteData.Values[ " controller " ].ToString(), // Controller
routeData, // Route data 上海闵行企业网站设计与制作>
htmlAttributes // HTML attributes to apply to hyperlink
)
%
Html.ActionLink方法会向浏览器端产生超链接的HTML。它的第一个参数为要显示的文字超链接的标题,这里通过ViewData[displayname]读取。
第二个和第三个参数指定了action和控制器去生成链接,我们使用Html.ViewContext.RouteData.Values[action].ToString
和Html.ViewContext.RouteData.Values[controller].ToString()来获得当前请求的action和控制器,而避免了硬编码。
第四个输入参数指定了路由的数据,这是我们用RouteValueDictionary字典的形式,分别向sortBy和ascending参数传入值。
最后的参数设定了生成链接的HTML的CSS样式。
当运行程序后,默认是按产品名称升序排序的,如下图。
当点产品标题列旁边的小箭头时,会向控制器发出如
Products/Sortable?sortBy=ProductNameascending=False的请求,于是产品名称按降序排序了,如下图:
本文的代码可以在:http://aspnet.4guysfromrolla.com/code/GridDemosMVC.zip下载得到。