ASP.NET MVC SportStore 购物网示例(4)

样式(Style)

以下是我们将要创建的网页布层:

Sports Store (Header)

主页

分类1

分类2

分类 3

产品1

产品2

产品3

设计Master Page

创建 Partial View (视图控件)

右键单击 Views/Shared 打开添加 View对话框。

clip_image001

ViewName为 ProductSummary选中Create a partial view。

编辑如下代码:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DomainModel.Entities.Product>" %>

<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<Product>)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<Product>)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<HttpContextBase>();

var mockRequest = new Moq.Mock<HttpRequestBase>();

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测试运行。

clip_image003

创建分类的导航菜单

创建一个Controller

public class NavController : Controller

{

public string Menu()

{

return "Hello from NavController";

}

}

更改Site.Master

<div id="categories">

<% Html.RenderAction("Menu", "Nav"); %>

</div>

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

IQueryable<Product> products = 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<IProductsRepository>();

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<NavLink>)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

List<NavLink> navLinks = new List<NavLink>();

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。

clip_image004

修改代码如下:

<% 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

List<NavLink> navLinks = new List<NavLink>();

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

<div id="categories">

<% Html.RenderAction("Menu", "Nav",

new { highlightCategory = ViewData["CurrentCategory"] }); %>

</div>

修改Menu.ascx

<% foreach (var link in Model)

{ %>

<a href="<%= Url.RouteUrl(link.RouteValues) %>" class="<%= link.IsSelected ? "selected" : "" %>">

<%= link.Text %>

</a>

<% } %>

创建购物车
定义购物车实体类
namespace DomainModel.Entities

{

public class Cart

{

private List<CartLine> lines = new List<CartLine>();

public IList<CartLine> Lines { 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 List<CartLine> lines = new List<CartLine>();

public IList<CartLine> Lines

{

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运行测试:

clip_image006

转载请注明出处! Author: im@xingquan.org

posted @ 2011-03-24 19:14  敏捷学院  阅读(768)  评论(0编辑  收藏  举报