样式(Style)
以下是我们将要创建的网页布层:
Sports Store (Header) |
|
主页 分类1 分类2 分类 3 |
产品1 产品2 产品3 |
设计Master Page
略
创建 Partial View (视图控件)
右键单击 Views/Shared 打开添加 View对话框。
ViewName为 ProductSummary选中Create a partial view。
编辑如下代码:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<div class="item">
<h3><%=Model.Name %>h3>
<%= Model.Description %>
<h4><%=Model.Price.ToString("c") %>h4>
div>
导航和购物车
添加导航控件
1. ProductsController’s List可以根据分类筛选产品。
2. 改进路由配置使得每个分类都有一个 "clean" URL.
3. 创建一个分类列表,高亮当前选中的列表,使用Html.RenderAction方法。
根据分类筛选产品列表
添加测试达到如下目的:
当 分类 是null时的时候List()返回所有产品
根据分类名称List()返回此分类的产品
[Test]
public void List_Includes_All_Products_When_Category_Is_Null()
{
// Set up scenario with two categories
IProductsRepository repository = MockProductsRepository(
new Product { Name = "Artemis", Category = "Greek" },
new Product { Name = "Neptune", Category = "Roman" }
);
ProductsController controller = new ProductsController(repository);
controller.PageSize = 10;
// Request an unfiltered list
var result = controller.List(null, 1);
// Check that the results include both items
Assert.IsNotNull(result, "Didn't render view");
var products = (IList)result.ViewData.Model;
Assert.AreEqual(2, products.Count, "Got wrong number of items");
Assert.AreEqual("Artemis", products[0].Name);
Assert.AreEqual("Neptune", products[1].Name);
}
[Test]
public void List_Filters_By_Category_When_Requested()
{
// Set up scenario with two categories: Cats and Dogs
IProductsRepository repository = MockProductsRepository(
new Product { Name = "Snowball", Category = "Cats" },
new Product { Name = "Rex", Category = "Dogs" },
new Product { Name = "Catface", Category = "Cats" },
new Product { Name = "Woofer", Category = "Dogs" },
new Product { Name = "Chomper", Category = "Dogs" }
);
ProductsController controller = new ProductsController(repository);
controller.PageSize = 10;
// Request only the dogs
var result = controller.List("Dogs", 1);
// Check the results
Assert.IsNotNull(result, "Didn't render view");
var products = (IList)result.ViewData.Model;
Assert.AreEqual(3, products.Count, "Got wrong number of items");
Assert.AreEqual("Rex", products[0].Name);
Assert.AreEqual("Woofer", products[1].Name);
Assert.AreEqual("Chomper", products[2].Name);
Assert.AreEqual("Dogs", result.ViewData["CurrentCategory"]);
}
修改 ProoductsController类的List方法
public ViewResult List(string category, int page)
{
var productsInCategory = (category == null)
? productsRepository.Products
: productsRepository.Products.Where(x => x.Category == category);
int numProducts = productsInCategory.Count();
ViewData["TotalPages"] = (int)Math.Ceiling((double)numProducts / PageSize);
ViewData["CurrentPage"] = page;
ViewData["CurrentCategory"] = category;
return View(productsInCategory
.Skip((page - 1) * PageSize)
.Take(PageSize)
.ToList()
);
}
使用url测试 http://myweb/?category=ball
在list.aspx 添加如下代码,显示当前商品的分类:
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
SportsSotre:
<%= string.IsNullOrEmpty((string)ViewData["CurrentCategory"])
? "All products"
: Html.Encode(ViewData["CurrentCategory"]) %>
asp:Content>
为 分类 自定义URL访问规则
自定义的url访问规则看起来会更好一些,例如:
Example URL Leads To
/ First page of “All products”
/Page2 Second page of “All products”
/Football First page of “Football” category
/Football/Page43 Forty-third page of “Football” category
/Anything/Else Else action on AnythingController
添加测试
[TestFixture]
public class InboundRoutingTests
{
public void TestRoute(string url, object expectedValues)
{
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
var mockHttpContext = new Moq.Mock();
var mockRequest = new Moq.Mock();
mockHttpContext.Setup(x => x.Request).Returns(mockRequest.Object);
mockRequest.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns(url);
//act:get the mapped routed
RouteData routeData = routes.GetRouteData(mockHttpContext.Object);
//assert:Test the route values against expectations
Assert.IsNotNull(routeData);
var expectedDict = new RouteValueDictionary(expectedValues);
foreach (var expectedVal in expectedDict)
{
if (expectedVal.Value == null)
Assert.IsNull(routeData.Values[expectedVal.Key]);
else
Assert.AreEqual(expectedVal.Value.ToString(),
routeData.Values[expectedVal.Key].ToString());
}
}
[Test]
public void Slash_Goes_To_All_Products_Page_1()
{
TestRoute("~/", new { controller = "Products", action = "List", category = (string)null, page = 1 });
}
[Test]
public void Page2_Goes_To_All_Products_Page_2()
{
TestRoute("~/Page2", new
{
controller = "Products",
action = "List",
category = (string)null,
page = 2
});
}
[Test]
public void Ball_Goes_To_ball_Page_1()
{
TestRoute("~/ball", new
{
controller = "Products",
action = "List",
category = "ball",
page = 1
});
}
[Test]
public void Ball_Slash_Page43_Goes_To_ball_Page_3()
{
TestRoute("~/ball/Page3", new
{
controller = "Products",
action = "List",
category = "ball",
page = 3
});
}
[Test]
public void Anything_Slash_Else_Goes_To_Else_On_AnythingController()
{
TestRoute("~/Anything/Else", new { controller = "Anything", action = "Else" });
}
}
修改 Global.asax.cs
routes.MapRoute(null,
"", // Only matches the empty URL (i.e. ~/)
new
{
controller = "Products",
action = "List",
category = (string)null,
page = 1
}
);
routes.MapRoute(null,
"Page{page}", // Matches ~/Page2, ~/Page123, but not ~/PageXYZ
new { controller = "Products", action = "List", category = (string)null },
new { page = @"\d+" } // Constraints: page must be numerical
);
routes.MapRoute(null,
"{category}", // Matches ~/Football or ~/AnythingWithNoSlash
new { controller = "Products", action = "List", page = 1 }
);
routes.MapRoute(null,
"{category}/Page{page}", // Matches ~/Football/Page567
new { controller = "Products", action = "List" }, // Defaults
new { page = @"\d+" } // Constraints: page must be numerical
);
routes.MapRoute(null, "{controller}/{action}");
最后修改List.aspx的翻页:
<%=Html.PageLinks((int)ViewData["CurrentPage"],(int)ViewData["TotalPages"],
i=>Url.Action("List",new {page=i,
category=ViewData["currentCategory"]
})) %>
F5测试运行。
创建分类的导航菜单
创建一个Controller
public class NavController : Controller
{
public string Menu()
{
return "Hello from NavController";
}
}
更改Site.Master
<% Html.RenderAction("Menu", "Nav"); %>
F5 运行。
显示实际的菜单,添加测试程序。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using WebUI.Controllers;
using DomainModel.Abstract;
using DomainModel.Entities;
using System.Web.Mvc;
namespace Tests
{
[TestFixture]
public class NavControllerTests
{
[Test]
public void Takes_IProductsRepository_As_Constructor_Param()
{
// This test "passes" if it compiles, so no Asserts are needed
new NavController((IProductsRepository)null);
}
[Test]
public void Produces_Home_Plus_NavLink_Object_For_Each_Distinct_Category()
{
// Arrange: Product repository with a few categories
IQueryableproducts = new[] {
new Product { Name = "A", Category = "Animal" },
new Product { Name = "B", Category = "Vegetable" },
new Product { Name = "C", Category = "Mineral" },
new Product { Name = "D", Category = "Vegetable" },
new Product { Name = "E", Category = "Animal" }
}.AsQueryable();
var mockProductsRepos = new Moq.Mock();
mockProductsRepos.Setup(x => x.Products).Returns(products);
var controller = new NavController(mockProductsRepos.Object);
// Act: Call the Menu() action
ViewResult result = controller.Menu();
// Assert: Check it rendered one NavLink per category
// (in alphabetical order)
var links = ((IEnumerable)result.ViewData.Model).ToList();
Assert.IsEmpty(result.ViewName); // Should render default view
Assert.AreEqual(4, links.Count);
Assert.AreEqual("Home", links[0].Text);
Assert.AreEqual("Animal", links[1].Text);
Assert.AreEqual("Mineral", links[2].Text);
Assert.AreEqual("Vegetable", links[3].Text);
foreach (var link in links)
{
Assert.AreEqual("Products", link.RouteValues["controller"]);
Assert.AreEqual("List", link.RouteValues["action"]);
Assert.AreEqual(1, link.RouteValues["page"]);
if (links.IndexOf(link) == 0) // is this the "Home" link?
Assert.IsNull(link.RouteValues["category"]);
else
Assert.AreEqual(link.Text, link.RouteValues["category"]);
}
}
}
}
修改Navcontroller类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Routing;
using DomainModel.Abstract;
namespace WebUI.Controllers
{
public class NavController : Controller
{
private IProductsRepository productsRepository;
public NavController(IProductsRepository productsRepository)
{
this.productsRepository = productsRepository;
}
public ViewResult Menu()
{
// Put a Home link at the top
ListnavLinks = new List ();
navLinks.Add(new CategoryLink(null));
// Add a link for each distinct category
var categories = productsRepository.Products.Select(x => x.Category);
foreach (string category in categories.Distinct().OrderBy(x => x))
navLinks.Add(new CategoryLink(category));
return View(navLinks);
}
}
public class NavLink // Represents a link to any arbitrary route entry
{
public string Text { get; set; }
public RouteValueDictionary RouteValues { get; set; }
}
public class CategoryLink : NavLink // Specifically a link to a product category
{
public CategoryLink(string category)
{
Text = category ?? "Home";
RouteValues = new RouteValueDictionary(new
{
controller = "Products",
action = "List",
category = category,
page = 1
});
}
}
}
右键单击 Menu()方法,为Menu添加一个PartialView。
修改代码如下:
<% foreach (var link in Model)
{ %>
<a href="<%= Url.RouteUrl(link.RouteValues) %>">
<%= link.Text %>
a>
<% } %>
添加样式到css:
DIV#categories A
{
font: bold 1.1em "Arial Narrow","Franklin Gothic Medium",Arial; display: block;
text-decoration: none; padding: .6em; color: Black;
border-bottom: 1px solid silver;
}
DIV#categories A.selected { background-color: #666; color: White; }
DIV#categories A:hover { background-color: #CCC; }
DIV#categories A.selected:hover { background-color: #666; }
高亮显示分类
修改代码:
public ViewResult Menu(string highlightCategory)
{
// Put a Home link at the top
ListnavLinks = new List ();
navLinks.Add(new CategoryLink(null)
{
IsSelected = (highlightCategory == null)
});
// Add a link for each distinct category
var categories = productsRepository.Products.Select(x => x.Category);
foreach (string category in categories.Distinct().OrderBy(x => x))
navLinks.Add(new CategoryLink(category)
{
IsSelected = (category == highlightCategory)
});
return View(navLinks);
}
}
public class NavLink // Represents a link to any arbitrary route entry
{
public string Text { get; set; }
public RouteValueDictionary RouteValues { get; set; }
public bool IsSelected { get; set; }
}
注意:更改测试 ViewResult result = contrller.Menu(null);
修改Site.master
categories"><% Html.RenderAction("Menu", "Nav",new { highlightCategory = ViewData["CurrentCategory"] }); %>
修改Menu.ascx
<% foreach (var link in Model)
{ %>
<%= Url.RouteUrl(link.RouteValues) %>" class="<%= link.IsSelected ? "selected" : "" %>">
<%= link.Text %>
<% } %>
创建购物车
定义购物车实体类
namespace DomainModel.Entities
{
public class Cart
{
private Listlines = new List ();
public IListLines { get { return lines; } }
public void AddItem(Product product, int quantity) { }
public decimal ComputeTotalValue() { throw new NotImplementedException(); }
public void Clear() { throw new NotImplementedException(); }
}
public class CartLine
{
public Product Product { get; set; }
public int Quantity { get; set; }
}
}
测试Cart
[TestFixture]
public class CartTests
{
[Test]
public void Cart_Starts_Empty()
{
Cart cart = new Cart();
Assert.AreEqual(0, cart.Lines.Count);
Assert.AreEqual(0, cart.ComputeTotalValue());
}
[Test]
public void Can_Add_Items_To_Cart()
{
Product p1 = new Product { ProductID = 1 };
Product p2 = new Product { ProductID = 2 };
// Add three products (two of which are same)
Cart cart = new Cart();
cart.AddItem(p1, 1);
cart.AddItem(p1, 2);
cart.AddItem(p2, 10);
// Check the result is two lines
Assert.AreEqual(2, cart.Lines.Count, "Wrong number of lines in cart");
// Check quantities were added properly
var p1Line = cart.Lines.Where(l => l.Product.ProductID == 1).First();
var p2Line = cart.Lines.Where(l => l.Product.ProductID == 2).First();
Assert.AreEqual(3, p1Line.Quantity);
Assert.AreEqual(10, p2Line.Quantity);
}
[Test]
public void Can_Be_Cleared()
{
Cart cart = new Cart();
cart.AddItem(new Product(), 1);
Assert.AreEqual(1, cart.Lines.Count);
cart.Clear();
Assert.AreEqual(0, cart.Lines.Count);
}
[Test]
public void Calculates_Total_Value_Correctly()
{
Cart cart = new Cart();
cart.AddItem(new Product { ProductID = 1, Price = 5 }, 10);
cart.AddItem(new Product { ProductID = 2, Price = 2.1M }, 3);
cart.AddItem(new Product { ProductID = 3, Price = 1000 }, 1);
Assert.AreEqual(1056.3, cart.ComputeTotalValue());
}
}
运行Test失败,修改Cart类
public class Cart
{
private Listlines = new List ();
public IListLines
{
get
{
return lines.AsReadOnly();
}
}
public void AddItem(Product product, int quantity)
{
var line = lines.FirstOrDefault(l => l.Product.ProductID == product.ProductID);
if (line == null)
lines.Add(new CartLine { Product = product, Quantity = quantity });
else
line.Quantity += quantity;
}
public decimal ComputeTotalValue()
{
return lines.Sum(l => l.Product.Price * l.Quantity);
}
public void Clear()
{
lines.Clear();
}
public void RemoveLine(Product product)
{
lines.RemoveAll(l => l.Product.ProductID == product.ProductID);
}
}
public class CartLine
{
public Product Product { get; set; }
public int Quantity { get; set; }
}
测试成功。
添加 "Add to Cart " 按钮
打开/Views/Shared/ProductSummary.ascx 添加 Add to Cart按钮
<div class="item">
<h3><%=Model.Name %>h3>
<%= Model.Description %>
<% using (Html.BeginForm("AddToCart", "Cart")){ %>
<%= Html.Hidden("ProductID");%>
<%=Html.Hidden("returnUrl", ViewContext.HttpContext.Request.Url.PathAndQuery)%>
<input type = "submit" value="+Add to cart" />
<% } %>
<h4><%=Model.Price.ToString("c") %>h4>
div>
添加样式
FORM { margin: 0; padding: 0; }
DIV.item FORM { float:right; }
DIV.item INPUT {
color:White; background-color: #333; border: 1px solid black; cursor:pointer;
}
F5运行测试:
转载请注明出处! Author: [email protected]