视图模型- PagingInfo 类:
public class PagingInfo { public int TotalItems { get; set; } public int ItemsPerPage { get; set; } public int CurrentPage { get; set; } public int TotalPages { get { return (int)Math.Ceiling((decimal)TotalItems / ItemsPerPage); } } }
创建 “HtmlHelpers” 文件夹,在 PagingHelper 类中定义 HTML 辅助器方法 PageLinks:
public static class PagingHelpers { /// <summary> /// HTML 的扩展方法,用来生成页面链接 /// </summary> /// <param name="html">第一个参数带“this”,表示这是一个扩展方法</param> /// <param name="pagingInfo">所需的分页信息</param> /// <param name="pageUrl">第3个参数是一个方法,该方法的输入参数是int类型(这里是页码),返回string类型(这里是输出URL)</param> /// <returns></returns> public static MvcHtmlString PageLinks(this HtmlHelper html, PagingInfo pagingInfo, Func<int, string> pageUrl) { StringBuilder result = new StringBuilder(); for (int i = 1; i <= pagingInfo.TotalPages; i++) { TagBuilder tag = new TagBuilder("a"); //添加一个<a>标签 tag.MergeAttribute("href", pageUrl(i)); //标签所指向的页数,第二个参数是一个方法,而方法的参数是当前的i tag.InnerHtml = i.ToString(); //标签显示的文本 if (i == pagingInfo.CurrentPage) { tag.AddCssClass("selected"); //对当前页的<a>标签添加值为selected的class属性 tag.AddCssClass("btn-primary"); } tag.AddCssClass("btn btn-default"); //对所有的<a>标签都添加值为“默认按钮”的class属性 result.Append(tag.ToString()); } return MvcHtmlString.Create(result.ToString()); } }
需要引入视图模型所在的命名空间
为更好的向视图传递数据,更好的做法是将控制器发送给视图的所有数据封装成一个单一的视图模型:
public class UserListViewModel { public IEnumerable<t_user> Users { get; set; } //页面显示需要的数据 public PagingInfo PagingInfo { get; set; } //分页信息
public string CurrentCategory { get; set; } //当前分类 }
在视图中添加的显示分页链接的代码:
<div> @Html.PageLinks(Model.PagingInfo, x => Url.Action("动作方法名", new { page = x, category = Model.CurrentCategory }))
<!-- 该扩展方法的第二个参数传递的是一个方法,该方法指明了在扩展方法的某一步中要实现什么功能,而且就像普通参数一样可以在循环中被重复使用 -->
<!-- 具体的是:在一个循环中,当当前值小于所需要的页面总数时,传入当前要生成的链接对应的页码(int 类型),生成对应的链接(包含了页码和分类) -->
<!-- 如果每次视图模型都带有 PagingInfo 属性,则方法中的第一个实参 “Model.PagingInfo” 就总是不用改变,而对于链接所生成的具体信息,全在于后面的动作方法名以及匿名对象中的属性名和对应的值 -->
<!-- new 的是一个路由值对象,可以填充路由设置中对应的部分 --> </div>
带有分页和并根据所选分类进行过滤的(Product)控制器:
public class ProductController : Controller { private IProductsRepository repository; //用于…… public int PageSize = 4; //分页中每页显示的项目数 public ProductController(IProductsRepository productRepository) //通过初始化器声明依赖项 { this.repository = productRepository; } public ViewResult List(string category,int page=1) { ProductsListViewModel model = new ProductsListViewModel { Products= repository.Products.Where(p=>category==null||p.Category==category).OrderBy(p => p.id).Skip((page - 1) * PageSize).Take(PageSize), PagingInfo=new PagingInfo { CurrentPage = page, ItemsPerPage = PageSize, TotalItems = category == null ? repository.Products.Count() : repository.Products.Where(e=>e.Category==category).Count() //将分类考虑进分页信息 }, CurrentCategory = category }; return View(model); } }
使用 SQLSuger 的简单类似实现:
public ActionResult GetComStaffs(int page=1)
{
UserListViewModel result = new UserListViewModel();
using (var db = new SqlSugarClient(DBHelper.connectionString))
{
var lusers = db.Queryable<t_user>().Where(u => u.Isdel == false).ToList(); //db.Queryable<t_user>().Where(u => u.Isdel == false).OrderBy(u=>u.id).ToPageList(page, 10);
result.Users = lusers.Skip((page - 1) * 10).Take(10);
result.PagingInfo = new PagingInfo { CurrentPage = page, TotalItems = lusers.Count,ItemsPerPage=10 };
}
return View(result);
}
调整 URL 方案 —— 修改 App_Start / RouteConfig.cs 文件中的 RegisterRoutes 方法:
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//重点看第二个参数(这个参数对应着实际的链接的形式),后面的匿名类型对象对应着当出现这种情况时自动补齐的值 routes.MapRoute(null, "", new { controller = "Product", action = "List", category = (string)null, page = 1 }); routes.MapRoute(null, "Page{page}", new { controller = "Product", action = "List", category = (string)null }, new { page = @"d+" }); routes.MapRoute(null, "{category}", new { controller = "Product", action = "List", page=1 }); routes.MapRoute(null, "{category}/Page{page}", new { controller = "Product", action = "List" }, new { page = @"d+" }); routes.MapRoute(null, "{controller}/{action}"); } }
生成分类列表(定义 “Nav” 控制器和 “Menu” 方法):
public class NavController : Controller { private IProductsRepository repository; public NavController(IProductsRepository repo) //构造器 { repository = repo; } public PartialViewResult Menu(string category=null) {
//这里是处于多样性的目的,更常规的做法是床架一个视图模型,它含有分类列表和所选分类 ViewBag.selectedCategory = category; //将选择的分类通过控制器的动作方法再传递给视图,用来 “高亮显示当前分类” IEnumerable<string> categories = repository.Products.Select(x => x.Category).Distinct().OrderBy(x => x); return PartialView(categories); //返回一个分部视图(分类导航菜单确实也只是整个页面的一部分) } }
在 “分类导航菜单” 控制器中添加一个构造器,其参数接受的是 IProductRepository 的一个实现。(其效果是声明了一个由 Ninject 进行解析的依赖项,用以创建这个 NavController 类的实例)
在 “Menu” 动作方法中使用 LINQ 查询从存储库获取分类列表,并将它们传递给视图。(返回的是分部视图)
分类导航菜单对应的分部视图:
@model IEnumerable<string> <!-- 添加了一个叫做“Home”的链接,它将出现在分类列表的顶部,并在无分类过滤器作用的情况下列举所有产品。(这是用 ActionLink 辅助器方法来实现的) --> @Html.ActionLink("Home","List","Product",null,new { @class = "btn btn-block btn-default btn-lg"}) <!-- 枚举分类名称,并用 RouteLink 方法为每个分类创建链接。—— 该辅助器方法与 ActionLink 类似,但在根据路由配置生成URL时,它让笔者能够有针对性地在匿名对象中提供一组“键/值”对,它们为路由的各个片段提供了数据 --> @foreach (var link in Model) { @Html.RouteLink(link,new { controller= "Product",action="List",category=link,page=1 },new { @class = "btn btn-block btn-default btn-lg"
+ (link == ViewBag.selectedCategory ? " btn-primary" : "") }) //如果当前link的值与SelectedCategory的值匹配,则为待创建的元素添加高亮样式 }
定义购物车实体(在文件夹 Entities 中定义):
public class Cart { private List<CartLine> lineCollection = new List<CartLine>(); public IEnumerable<CartLine> Lines { get { return lineCollection; } } //通过该属性对购物车的内容进行访问 //添加 public void AddItem(Product product, int quantity) { CartLine line = lineCollection.Where(p => p.Product.id == product.id).FirstOrDefault(); //添加之前要分购物车里是否已经有该商品 if (line == null) { lineCollection.Add(new CartLine { Product = product, Quantity = quantity }); } else { line.Quantity += quantity; } } //移除 public void RemoveLine(Product product) { lineCollection.RemoveAll(l => l.Product.id == product.id); } //计算总价 public decimal ComputeTotalValue() { return lineCollection.Sum(e => e.Product.Price * e.Quantity); } //清空 public void Clear() { lineCollection.Clear(); } } public class CartLine //表示又客户所选的一个产品和用户想要购买的数量 { public Product Product { get; set; } public int Quantity { get; set; } }
产品信息列表 + “加入购物车”按钮:
@model SportsStore.Domain.Entities.Product <div class="well"> <h3> <strong>@Model.Name</strong> <span class="pull-right label label-primary">@Model.Price.ToString("c")</span> </h3> <!-- 添加“加入购物车”按钮 --> @using (Html.BeginForm("AddToCart", "Cart")) { <div class="pull-right"> <!-- 给表单添加一组隐藏的子标签 --> @Html.HiddenFor(x => x.id) @Html.Hidden("returnUrl",Request.Url.PathAndQuery) <!-- “加入购物车”按钮 --> <input type="submit" class="btn-success" value="加入购物车" /> </div> } <span class=" lead">@Model.Description</span> </div>
实现购物车控制器(CartController):
public class CartController : Controller { private IProductsRepository repository; //带有依赖项的构造器 public CartController(IProductsRepository repo) { repository = repo; } public ViewResult Index(string returnUrl) { return View(new CartIndexViewModel { Cart = GetCart(), ReturnUrl = returnUrl }); } #region 对于这两个方法,这里使用了与 HTML 表单中 input 元素相匹配的参数名(分部视图ProductSummary中的表单),这可以让 MVC框架将输入的表单的 POST变量自动与这些参数关联起来。(笔者不需要自己去处理) public RedirectToRouteResult AddToCart(int id, string returnUrl) { Product product = repository.Products.FirstOrDefault(p => p.id == id); if (product != null) { GetCart().AddItem(product, 1); } return RedirectToAction("Index", new { returnUrl }); } public RedirectToRouteResult RemoveFromCart(int id, string returnUrl) { Product product = repository.Products.FirstOrDefault(p => p.id == id); if (product != null) { GetCart().RemoveLine(product); } return RedirectToAction("Index", new { returnUrl }); } #endregion //获取购物车 private Cart GetCart() { Cart cart = (Cart)Session["Cart"]; if (cart == null) { cart = new Cart(); Session["Cart"] = cart; //使用ASP.NET的会话状态特性存储和接收Cart对象(本程序中的购物车并没有保存的数据库——持久性不够) } return cart; } }
显示购物车所需视图模型:
public class CartIndexViewModel { public Cart Cart { get; set; } public string ReturnUrl { get; set; } }
显示购物车内容的视图:
@model SportsStore.WebUI.Models.CartIndexViewModel @{ ViewBag.Title = "购物车"; } <h2>购物车</h2> <table class="table">
<!-- 表头 --> <thead> <tr> <th>商品</th> <th>数量</th> <th class="text-right">单价</th> <th class="text-right">总价</th> </tr> </thead>
<!-- 表格内容。给这些元素的 class 属性所赋的值用于表格和元素的对齐方式 --> <tbody> @foreach (var line in Model.Cart.Lines) { <tr> <td class="text-left">@line.Product.Name</td> <td class="text-center">@line.Quantity</td> <td class="text-right">@line.Product.Price.ToString("c")</td> <td class="text-right">@((line.Quantity*line.Product.Price).ToString("c"))</td> </tr> } </tbody>
<!-- 表格底部 --> <tfoot> <tr> <td colspan="3" class="text-right">合计:</td> <td class="text-right">@Model.Cart.ComputeTotalValue().ToString("c")</td> </tr> </tfoot> </table> <div class="text-center"> <a class="btn btn-primary" href="@Model.ReturnUrl">继续购买</a> </div>
该视图枚举了购物车中的各条信息,并为各条信息添加了一个表格行。