您的位置:首页 > 其它

构建一个真实的应用电子商务SportsStore4

2013-06-03 16:40 387 查看

构建一个真实的应用电子商务SportsStore(四)

上篇中,我们将数据库中的数据显示到了 UI上,在这里我要强调一点,在上篇中我们应用了强类型的View,不要与model业务混淆,有关强类型view的知识点,不在本实例范畴之内,请参阅相关文档。对于任何一个电子商务网站来说,都需要使用户能方便的浏览所有的商品,并能够从一页迁移到另一页,这是个非常实用、也非常基本的功能,但在MVC4中,怎么实现它呢,现在就让我们一步一步的完善这个功能。

首先,我们要为我们的Product控制器的List 方法添加一个参数,用它来代表浏览的页号,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Models;

namespace SportsStore.WebUI.Controllers
{
public class ProductController : Controller
{
private IProductsRepository repository;
public int PageSize = 4;

public ProductController(IProductsRepository productRepository)
{
this.repository = productRepository;
}

public ViewResult List(int page = 1) {

return View(repository.Products.OrderBy(p => p.ProductID)
.Skip((page - 1) * PageSize)
.Take(PageSize));
}

}
}


PageSize字段指定了每一页要显示的产品数量,稍后我们将使用更好的机制来替换它,现在你只需要理解它。
我们还添加了一个可选的参数到List方法,这就表示如果我们调用的方法没有参数(List()), 我们将会使用(List(1)) 来处理,就是默认为显示第一页。

现在我们添加一个测试文件到你的SportsStore.UnitTests工程

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Controllers;
using System.Collections.Generic;
using System.Linq;
using SportsStore.WebUI.Models;
using System;

namespace SportsStore.UnitTests {
[TestClass]
public class UnitTest1 {
[TestMethod]
public void Can_Paginate() {
// Arrange
Mock<IProductsRepository> mock = new Mock<IProductsRepository>();
mock.Setup(m => m.Products).Returns(new Product[] {
new Product {ProductID = 1, Name = "P1"},
new Product {ProductID = 2, Name = "P2"},
new Product {ProductID = 3, Name = "P3"},
new Product {ProductID = 4, Name = "P4"},
new Product {ProductID = 5, Name = "P5"}
}.AsQueryable());
ProductController controller = new ProductController(mock.Object);
controller.PageSize = 3;
// Act
IEnumerable<Product> result =
(IEnumerable<Product>)controller.List(2).Model;
// Assert
Product[] prodArray = result.ToArray();
Assert.IsTrue(prodArray.Length == 2);
Assert.AreEqual(prodArray[0].Name, "P4");
Assert.AreEqual(prodArray[1].Name, "P5");
}

}
}


这里请注意看,我们是如何轻松的从一个控制器的结果集中获得数据的,在这里,我们反转了这个结果集到一个数组,并检查单个对象的长度和值。
运行工程,可以看到如下结果:



添加View Model
View Model 不是我们领域模型的一部分,只是为了方便在控制器和View 之间传递数据,所以我们把它放在SportsStore.WebUI工程的Models文件夹中,命名为PagingInfo,代码如下:

using System;

namespace SportsStore.WebUI.Models {

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); }
}

}
}


添加HTML Helper 方法
现在,我们需要添加一个文件夹命名为HtmlHelpers,并添加一个文件,命名为PagingHelpers。

using System;
using System.Text;
using System.Web.Mvc;
using SportsStore.WebUI.Models;
namespace SportsStore.WebUI.HtmlHelpers
{
public static class PagingHelpers
{
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"); // Construct an <a> tag
tag.MergeAttribute("href", pageUrl(i));
tag.InnerHtml = i.ToString();
if (i == pagingInfo.CurrentPage)
tag.AddCssClass("selected");
result.Append(tag.ToString());
}
return MvcHtmlString.Create(result.ToString());
}
}
}


这个PageLinks 扩展方法为页链接集合产生HTML,这个页链接集合使用了PagingInfo对象, Func 参数提供了传递代理的能力,代理被用来产生链接到其他页面的链接。

测试我们的HtmlHelpers
在我们测试文件中添加如下引用和方法:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Controllers;
using System.Collections.Generic;
using System.Linq;
using SportsStore.WebUI.Models;
using System;
using System.Web.Mvc;
using SportsStore.WebUI.HtmlHelpers;

namespace SportsStore.UnitTests {
[TestClass]
public class UnitTest1 {
[TestMethod]
public void Can_Paginate() {
// Arrange
Mock<IProductsRepository> mock = new Mock<IProductsRepository>();
mock.Setup(m => m.Products).Returns(new Product[] {
new Product {ProductID = 1, Name = "P1"},
new Product {ProductID = 2, Name = "P2"},
new Product {ProductID = 3, Name = "P3"},
new Product {ProductID = 4, Name = "P4"},
new Product {ProductID = 5, Name = "P5"}
}.AsQueryable());
ProductController controller = new ProductController(mock.Object);
controller.PageSize = 3;
// Act
IEnumerable<Product> result =
(IEnumerable<Product>)controller.List(2).Model;
// Assert
Product[] prodArray = result.ToArray();
Assert.IsTrue(prodArray.Length == 2);
Assert.AreEqual(prodArray[0].Name, "P4");
Assert.AreEqual(prodArray[1].Name, "P5");
}

[TestMethod]
public void Can_Generate_Page_Links()
{
// Arrange - define an HTML helper - we need to do this
// in order to apply the extension method
HtmlHelper myHelper = null;
// Arrange - create PagingInfo data
PagingInfo pagingInfo = new PagingInfo
{
CurrentPage = 2,
TotalItems = 28,
ItemsPerPage = 10
};
// Arrange - set up the delegate using a lambda expression
Func<int, string> pageUrlDelegate = i => "Page" + i;
// Act
MvcHtmlString result = myHelper.PageLinks(pagingInfo, pageUrlDelegate);
// Assert
Assert.AreEqual(result.ToString(), @"<a href=""Page1"">1</a>"
+ @"<a class=""selected"" href=""Page2"">2</a>"
+ @"<a href=""Page3"">3</a>");
}
}
}


要使扩展方法有效,在代码中,我们需要确保引用它所在的namespace,我使用了using语句,但是对于一个 Razor View,我们必须配置web.config文件,而一个MVC工程,有2个web.config文件,一个是在工程的根目录下,另一个在View文件夹中,我们需要配置文件夹中的这个,添加如下语句到namespace标签中:
<add namespace="SportsStore.WebUI.HtmlHelpers"/>

<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="SportsStore.WebUI.HtmlHelpers"/>
</namespaces>


添加View Model数据
我们并没有打算完全使用HTML helper方法,我们依然需要提供一个PagingInfo view model类的实例到View,我们可以使用view bag的特性,但我们更倾向于打包所有的Controller数据发送到一个单一的View,要实现这一点,我们要添加一个新类到Model文件夹,命名为ProductsListViewModel。

好了,现在我们再更新一下ProductController的代码,用ProductsListViewModel类提供给View更详细的数据。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Models;

namespace SportsStore.WebUI.Controllers
{
public class ProductController : Controller
{
private IProductsRepository repository;
public int PageSize = 4;

public ProductController(IProductsRepository productRepository)
{
this.repository = productRepository;
}

public ViewResult List(int page = 1) {

ProductsListViewModel model = new ProductsListViewModel
{
Products = repository.Products
.OrderBy(p => p.ProductID)
.Skip((page - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = repository.Products.Count()
}
};
return View(model);
}

}
}


修改测试文件代码:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Controllers;
using System.Collections.Generic;
using System.Linq;
using SportsStore.WebUI.Models;
using System;
using System.Web.Mvc;
using SportsStore.WebUI.HtmlHelpers;

namespace SportsStore.UnitTests {
[TestClass]
public class UnitTest1 {
[TestMethod]
public void Can_Paginate() {
// Arrange
Mock<IProductsRepository> mock = new Mock<IProductsRepository>();
mock.Setup(m => m.Products).Returns(new Product[] {
new Product {ProductID = 1, Name = "P1"},
new Product {ProductID = 2, Name = "P2"},
new Product {ProductID = 3, Name = "P3"},
new Product {ProductID = 4, Name = "P4"},
new Product {ProductID = 5, Name = "P5"}
}.AsQueryable());
ProductController controller = new ProductController(mock.Object);
controller.PageSize = 3;

// Action
ProductsListViewModel result = (ProductsListViewModel)controller.List(2).Model;

// Assert
Product[] prodArray = result.Products.ToArray();
Assert.IsTrue(prodArray.Length == 2);
Assert.AreEqual(prodArray[0].Name, "P4");
Assert.AreEqual(prodArray[1].Name, "P5");
}

[TestMethod]
public void Can_Send_Pagination_View_Model()
{
// Arrange
Mock<IProductsRepository> mock = new Mock<IProductsRepository>();
mock.Setup(m => m.Products).Returns(new Product[] {
new Product {ProductID = 1, Name = "P1"},
new Product {ProductID = 2, Name = "P2"},
new Product {ProductID = 3, Name = "P3"},
new Product {ProductID = 4, Name = "P4"},
new Product {ProductID = 5, Name = "P5"}
}.AsQueryable());
// Arrange
ProductController controller = new ProductController(mock.Object);
controller.PageSize = 3;
// Act
ProductsListViewModel result = (ProductsListViewModel)controller.List(2).Model;
// Assert
PagingInfo pageInfo = result.PagingInfo;
Assert.AreEqual(pageInfo.CurrentPage, 2);
Assert.AreEqual(pageInfo.ItemsPerPage, 3);
Assert.AreEqual(pageInfo.TotalItems, 5);
Assert.AreEqual(pageInfo.TotalPages, 2);
}

[TestMethod]
public void Can_Generate_Page_Links()
{
// Arrange - define an HTML helper - we need to do this
// in order to apply the extension method
HtmlHelper myHelper = null;
// Arrange - create PagingInfo data
PagingInfo pagingInfo = new PagingInfo
{
CurrentPage = 2,
TotalItems = 28,
ItemsPerPage = 10
};
// Arrange - set up the delegate using a lambda expression
Func<int, string> pageUrlDelegate = i => "Page" + i;
// Act
MvcHtmlString result = myHelper.PageLinks(pagingInfo, pageUrlDelegate);
// Assert
Assert.AreEqual(result.ToString(), @"<a href=""Page1"">1</a>"
+ @"<a class=""selected"" href=""Page2"">2</a>"
+ @"<a href=""Page3"">3</a>");
}
}
}


运行程序,你将看到如下结果:



改进我们的页面链接
我们的页面链接都是工作的,但它看起来是这样的:
http://localhost/?page=2
我们能做的更好些, 尤其是通过创建一个scheme,它遵循可组装URLs. 这使得用户更容易理解,并且更有效率,它看起来应该像下面的地址:
http://localhost/Page2
MVC很容易去改变URL scheme,因为它使用了ASP.NET的routing特性,我们要做的就是添加一个新的route 到RouteConfig.cs文件的RegisterRoutes,打开App_Start文件夹,找到我们要改的文件。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace SportsStore.WebUI
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
name: null,
url: "Page{page}",
defaults: new { Controller = "Product", action = "List" }
);

routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
);
}
}
}


把我的Route放在默认的Route前是很重要的,Route的处理是按照它们被列出的顺序进行的,我们需要用我们新的Route优先于默认的,现在你暂时先了解这些,以后,我们会更加详细的讲解它。

好了,这个博客的编辑器太难用了,总是不停的刷新,无法固定页面位置,今天的内容比较多,希望大家能仔细看好每一步,这里面没有一个字是多余的!剩下没写完的下次再写吧!请继续关注的续篇。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐