在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
注:此文翻译的比较仓促,个别用语没有统一,也没有时间对译文做复查。 原作者的源代码是基于mvc2写的,我本机环境是mvc1,所以手工将源代码做了转换,提供下载的源代码里有两个目录,其中MVC2Grid目录下是原作者提供的代码,在我本地没有测试(基于MVC2),MVCGrid是我转换过的,在本地编译通过,运行成功。 代码下载:/Files/sansi/Mvc2Grid.rar
任何网站应用程序的首先的需求之一是在表格里展示数据。在web form时代这是个简单的任务,直接在.aspx页面中放置GridView控件,配置一下数据源就可以了。但是这种方法你丢掉了最重要的东西。对于生成的HTML代码没有足够的控件权,而且没有书签功能因为分页和排序信息要被存储在viewstate,这也是在开发web form程序的另一个问题。在此帖中,我将展示如何建立拥有GridView所有功能(像排序和分页)的Data Grid Helper方法,同时你还有对生成的HTML足够的控制权,并且你所做的这所有的一切都是可以测试的方法,在想分享某一页数据的时候使用书签功能。 为了在MVC.NET中创建任何HTML 都可以使用的辅助方法,你需要写一个HtmlHelpler类的扩展方法。因为我不想导入任何命名空间和web.config文件,我使用static类在System.Web.Mvc.Html命名空间里创建一个扩展方法。
首先我将展示一个有排序功能的Grid Data Helper,然后我将创建一个Pager Helper为这个Grid增加分页功能。我使用的Grid Data Helper代码是Stephen Walther在他的ASP.NET MVC framework Unleashed书中提到的,分页部分我将使用Steven Sanderson在他的Pro ASP.NET MVC framework书提到的代码,该代码为分页链接生成HTML标记。我将扩展这段代码创建动态分页,并且使用Dynamic Linq示例代码写简单的排序逻辑,避免使用太多的switch case代码块。 让我们先看一下代码,然后研究它是如何工作的:
Grid Data Helper
using System;
using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.IO; namespace System.Web.Mvc.Html { public static class DataGridHelper { public static string DataGrid<T>(this HtmlHelper helper) { return DataGrid<T>(helper, null, null); } public static string DataGrid<T>(this HtmlHelper helper, object data) { return DataGrid<T>(helper, data, null); } public static string DataGrid<T>(this HtmlHelper helper, object data, string[] columns) { //Get items var items = (IEnumerable<T>)data; if (items == null) items = (IEnumerable<T>)helper.ViewData.Model; //Get column names if (columns == null) columns = typeof(T).GetProperties().Select(p => p.Name).ToArray(); //Create HtmlTextWriter var writer = new HtmlTextWriter(new StringWriter()); //Open table tag writer.RenderBeginTag(HtmlTextWriterTag.Table); //Render Table Header writer.RenderBeginTag(HtmlTextWriterTag.Thead); RenderHeader(helper, writer, columns); writer.RenderEndTag(); // Render table body writer.RenderBeginTag(HtmlTextWriterTag.Tbody); foreach (var item in items) RenderRow<T>(helper, writer, columns, item); writer.RenderEndTag(); //Close table tag writer.RenderEndTag(); //return the string return writer.InnerWriter.ToString(); } private static void RenderHeader(HtmlHelper helper, HtmlTextWriter writer, string[] columns) { writer.RenderBeginTag(HtmlTextWriterTag.Tr); foreach (var columnName in columns) { writer.RenderBeginTag(HtmlTextWriterTag.Th); var currentAction = (string)helper.ViewContext.RouteData.Values["action"]; var link = helper.ActionLink(columnName, currentAction, new { sort = columnName }); writer.Write(link); writer.RenderEndTag(); } writer.RenderEndTag(); } public static void RenderRow<T>(HtmlHelper helper, HtmlTextWriter write, string[] columns, T item) { write.RenderBeginTag(HtmlTextWriterTag.Tr); foreach (var columnName in columns) { write.RenderBeginTag(HtmlTextWriterTag.Td); var value = typeof(T).GetProperty(columnName).GetValue(item, null) ?? String.Empty; write.Write(helper.Encode(value.ToString())); write.RenderEndTag(); } write.RenderEndTag(); } } }
这个helpers的核心代码仅仅创建一个表格。正如你看到的这个helpers是泛型方法,你可以在所有的领域模型类中使用。你可以通过参数传递模型数据和列名,或者你仅仅通过ViewModel传递让helper为你做这个事。该第一个有趣的部分是我们使用了反射查找我们模型的列名。
columns = typeof(T).GetProperties().Select(p => p.Name).ToArray();
在这行代码中,我们获取所有的传递给helper模型的所有属性名称,把它放到列数据组,然后我们使用这个数据创建我们Grid表头的HTML代码,并且我们将使用它生成的必须的用来调用action方法的链接,将列名做为排序表达式传递给它。 然后我们开始创建标记,正如你所看到的我们使用HtmlTextWriter类型实现这个任务更加简洁,可读,更少错误验证,当然也更加可测。但你也可以使用StringBuiler类或者其它合作你觉得舒服的方法。 你所看到更加有兴趣的代码是RenderHeader和RenderRow方法,让我们来看一下:
var currentAction = (string)helper.ViewContext.RouteData.Values[“action”];
然后我们使用 Html.ActionLink辅助方法生成实际的链接标记:
var link = helper.ActionLink(columnName, currentAction, new { sort = columnName });
RenderRow:在这个方法中我使用反射获取方法的属性填充 每个单元格,并且我们使用列数组指定属性的名称:
var value = typeof(T).GetProperty(columnName).GetValue(item, null) ?? String.Empty;
同样注意我们使用Html.Encode方法传递合法的Html标记到视图避免XSS或者其它JS攻击: write.Write(helper.Encode(value.ToString()));
这样我们包含排序的GridData辅助方法就完成了,但分页怎么办呢?
代码
namespace System.Web.Mvc.Html
{ public static class PageLinkHelper { public static string PageLink(this HtmlHelper html, int currentPage, int totalPages, Func<int, string> pageUrl) { var diff = 1; StringBuilder result = new StringBuilder(); var TotalPages = totalPages; if (currentPage < 1) currentPage = 1; if ((currentPage + diff) < totalPages) totalPages = currentPage + diff; var startPage = 1; if ((currentPage - diff) > startPage) startPage = currentPage - diff; if ((currentPage - diff) > 1) { TagBuilder tag3 = new TagBuilder("a"); tag3.Attributes.Add("href", pageUrl(1)); tag3.InnerHtml = "1"; result.AppendLine(tag3.ToString()); } if ((currentPage - (diff + 1)) > 1) { TagBuilder tag2 = new TagBuilder("a"); tag2.Attributes.Add("href", pageUrl(currentPage - (diff + 1))); tag2.InnerHtml = "..."; result.AppendLine(tag2.ToString()); } for (int i = startPage; i <= totalPages; i++) { TagBuilder tag = new TagBuilder("a"); tag.Attributes.Add("href", pageUrl(i)); tag.InnerHtml = i.ToString(); if (i == currentPage) tag.AddCssClass("pageSelected"); result.AppendLine(tag.ToString()); } if ((currentPage + (diff + 1)) < TotalPages) { TagBuilder tag2 = new TagBuilder("a"); tag2.Attributes.Add("href", pageUrl(currentPage + (diff + 1))); tag2.InnerHtml = "..."; result.AppendLine(tag2.ToString()); } if ((currentPage + diff) < TotalPages) { TagBuilder tag3 = new TagBuilder("a"); tag3.Attributes.Add("href", pageUrl(TotalPages)); tag3.InnerHtml = TotalPages.ToString(); result.AppendLine(tag3.ToString()); } return result.ToString(); } } } 这个辅助方法获取当前页和总页数,还有一个方法创建链接。
代码
for (int i = startPage; i <= totalPages; i++)
{ TagBuilder tag = new TagBuilder("a"); tag.Attributes.Add("href", pageUrl(i)); tag.InnerHtml = i.ToString(); if (i == currentPage) tag.AddCssClass("pageSelected"); result.AppendLine(tag.ToString()); } 在这里我们创建每一页的标记,为当前页加上CSS类,剩余的代码只是在创建"..."链接,检查当前页与首页或末页的区别。我创建只是显示首页,末页和当前页的逻辑,还有上一页和下一页的分页链接和获取更多分页链接的"..."。很明显,你可以改变它实现你的需求,我期待在能在评论中看到更多更有效更好的实现这些逻辑的方法。 到此为止我们所做的所有事情只是生成在视图上使用的基本的HTML标记!我们需要在控制器中创建核心的逻辑。但是首先我想解释一些重要的观点。首先,在这个示例中我将使用默认的路由配置,并且借助于Repository和视图模型模式。我使用的数据库是Northwind示例数据库,我使用EF和ORM,并且使用简单的DI模式从repository中分离控制器,使得它仅仅在依赖控制的结构。
好,让我们来检查一下控制器的源代码后看看它是如何工作的:
代码
namespace Mvc2Application1.Controllers
{ public class ProductController : Controller { private IProductsRepository _repository; private int _pageSize; public ProductController():this(new ProductsRepositoryEF()) { } public ProductController(IProductsRepository repository) { _repository = repository; _pageSize = 10; } public ActionResult List(string sort, int? page) { var currentPage = page ?? 1; ViewData["SortItem"] = sort; sort = sort ?? "Name"; ViewData["CurrentPage"] = currentPage; ViewData["TotalPages"] = (int)Math.Ceiling((float)_repository.Products.Count() / _pageSize); var products = from p in _repository.Products select new ProductViewModel() { Name = p.ProductName, Price = p.UnitPrice ?? 0, Category = p.Category.CategoryName }; var sortedProducts = products .OrderBy(sort) .Skip((currentPage - 1) * _pageSize) .Take(_pageSize); return View(sortedProducts); } } }
你看到,我们通过参数传递排序和分页信息给动作方法,通过ViewData字典传递SortItem,CurrentPage和TotalPages给视图。我使用实图模型模式传递所需的列给视图,并且让复杂的对象相对简单的类。最有趣部分是取代传递lambda表达式,我传递了属性名做为排序条件。如果你尝试这样会得到错误。为了能够通过LINQ使用这种动态查询,你需要添加包括在Visual Studio中的Dynamic.cs类到你的工程 ,并且为你的控制器类添加System.Linq.Dynamic 命名空间,更多有关使用DynamicLinq的请参考Scott Guthrie’s blog 文章。这样我们的控制器足够简单并能工作,让我们转到视图代码完成我们的杰作吧。
代码
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Mvc2Application1.Models.ViewModels.ProductViewModel>>" %>
<%@ Import Namespace="Mvc2Application1.Models.ViewModels" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> List </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>List of Product</h2> <%=Html.DataGrid<ProductViewModel>() %> <div> <%=Html.PageLink((int)ViewData["CurrentPage"], (int)ViewData["TotalPages"], p => Url.Action("List", new { page = p, sort = (string)ViewData["SortItem"] }))%> </div> </asp:Content> 首先,请注意我导入了必须的命名空间。你看到视图的代码非常简单,我们通过Html.DataGrid助力展示Data Grid,对于分页部分我们只是调用了刚刚创建的Html.PageLink助手。最有趣的部分是我们传递给这个助手的方法: p => Url.Action("List",new { page = p,sort = (string)ViewData["SortItem"]})
这种方法让每个分页链接调用同一个动作方法(List),传递分页和排序参数,这样用户能通过传递Url的方式共享内容,因为所有的分页和排序信息都可以在地址览中看到。这就是我们刚刚创建的 Data Grid,现在我们可以完全控制生成的HTML,并且我们可以轻松的测试我们的助手类。正如Rob Connery说的,每一次在你的视图中有一个if就创建一个助手类!这种你的视图更容易测试,并且HTML更友好。
原文地址:http://mvcsharp.wordpress.com/2010/02/11/building-a-data-grid-in-asp-net-mvc/
|
请发表评论