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

定义一个订单提供 IoC 组件

在DomainModel项目中新建文件夹Services添加以下接口:

namespace DomainModel.Services

{

public interface IOrderSubmitter

{

void SubmitOrder(Cart cart);

}

}

修改CartController添加IOrderSubmitter接口。

private IOrderSubmitter orderSubmitter;

public CartController(IProductsRepository productsRepository, IOrderSubmitter orderSubmitter)

{

this.productsRepository = productsRepository;

this.orderSubmitter = orderSubmitter;

}

修改测试中的代码,添加新的测试。

[Test]

public void

Submitting_Order_With_No_Lines_Displays_Default_View_With_Error()

{

// Arrange

CartController controller = new CartController(null, null);

Cart cart = new Cart();

// Act

var result = controller.CheckOut(cart, new FormCollection());

// Assert

Assert.IsEmpty(result.ViewName);

Assert.IsFalse(result.ViewData.ModelState.IsValid);

}

[Test]

public void

Submitting_Empty_Shipping_Details_Displays_Default_View_With_Error()

{

// Arrange

CartController controller = new CartController(null, null);

Cart cart = new Cart();

cart.AddItem(new Product(), 1);

// Act

var result = controller.CheckOut(cart, new FormCollection {

{ "Name", "" }

});

// Assert

Assert.IsEmpty(result.ViewName);

Assert.IsFalse(result.ViewData.ModelState.IsValid);

}

[Test]

public void

Valid_Order_Goes_To_Submitter_And_Displays_Completed_View()

{

// Arrange

var mockSubmitter = new Moq.Mock<IOrderSubmitter>();

CartController controller = new CartController(null, mockSubmitter.Object);

Cart cart = new Cart();

cart.AddItem(new Product(), 1);

var formData = new FormCollection {

{ "Name", "Steve" }, { "Line1", "123 My Street" },

{ "Line2", "MyArea" }, { "Line3", "" },

{ "City", "MyCity" }, { "State", "Some State" },

{ "Zip", "123ABCDEF" }, { "Country", "Far far away" },

{ "GiftWrap", bool.TrueString }

};

// Act

var result = controller.CheckOut(cart, formData);

// Assert

Assert.AreEqual("Completed", result.ViewName);

mockSubmitter.Verify(x => x.SubmitOrder(cart));

Assert.AreEqual(0, cart.Lines.Count);

}

在CartController中添加POST方法的CheckOut。

[AcceptVerbs(HttpVerbs.Post)]

public ViewResult CheckOut(Cart cart, FormCollection form)

{

// Empty carts can't be checked out

if (cart.Lines.Count == 0)

{

ModelState.AddModelError("Cart", "Sorry, your cart is empty!");

return View();

}

// Invoke model binding manually

if (TryUpdateModel(cart.ShippingDetails, form.ToValueProvider()))

{

orderSubmitter.SubmitOrder(cart);

cart.Clear();

return View("Completed");

}

else // Something was invalid

return View();

}

添加一个模拟提交

namespace DomainModel.Services

{

public class FakeOrderSubmitter : IOrderSubmitter

{

public void SubmitOrder(DomainModel.Entities.Cart cart)

{

}

}

}

修改Web.config文件。

<component id="OrderSubmitter"

service="DomainModel.Services.IOrderSubmitter, DomainModel"

type="DomainModel.Services.FakeOrderSubmitter, DomainModel" />

添加样式

.field-validation-error { color: red; }

.input-validation-error { border: 1px solid red; background-color: #ffeeee; }

.validation-summary-errors { font-weight: bold; color: red; }

F5运行测试。

添加“Thanks for you roder”View

右键单击Cart文件夹,添加Complted视图。

clip_image001

使用 EmailOrderSumbitter来替换 FakeOrderSubmitter

在DomainModel项目中添加 EmailOrderSubmitter到Services文件夹。

public class EmailOrderSubmitter : IOrderSubmitter

{

const string MailSubject = "New order submitted!";

string smtpServer, mailFrom, mailTo;

public EmailOrderSubmitter(string smtpServer, string mailFrom, string mailTo)

{

// Receive parameters from IoC container

this.smtpServer = smtpServer;

this.mailFrom = mailFrom;

this.mailTo = mailTo;

}

public void SubmitOrder(Cart cart)

{

// Prepare the message body

StringBuilder body = new StringBuilder();

body.AppendLine("A new order has been submitted");

body.AppendLine("---");

body.AppendLine("Items:");

foreach (var line in cart.Lines)

{

var subtotal = line.Product.Price * line.Quantity;

body.AppendFormat("{0} x {1} (subtotal: {2:c}", line.Quantity,

line.Product.Name,

subtotal);

}

body.AppendFormat("Total order value: {0:c}", cart.ComputeTotalValue());

body.AppendLine("---");

body.AppendLine("Ship to:");

body.AppendLine(cart.ShippingDetails.Name);

body.AppendLine(cart.ShippingDetails.Line1);

body.AppendLine(cart.ShippingDetails.Line2 ?? "");

body.AppendLine(cart.ShippingDetails.Line3 ?? "");

body.AppendLine(cart.ShippingDetails.City);

body.AppendLine(cart.ShippingDetails.State ?? "");

body.AppendLine(cart.ShippingDetails.Country);

body.AppendLine(cart.ShippingDetails.Zip);

body.AppendLine("---");

body.AppendFormat("Gift wrap: {0}",

cart.ShippingDetails.GiftWrap ? "Yes" : "No");

// Dispatch the email

SmtpClient smtpClient = new SmtpClient(smtpServer);

smtpClient.Send(new MailMessage(mailFrom, mailTo, MailSubject,body.ToString()));

}

}

更新web.config文件

<component id="OrderSubmitter"

service="DomainModel.Services.IOrderSubmitter, DomainModel"

type="DomainModel.Services.EmailOrderSubmitter, DomainModel" >

<parameters>

<smtpServer>127.0.0.1</smtpServer>

<!-- Your server here -->

<mailFrom>sportsstore@example.com</mailFrom>

<mailTo>admin@example.com</mailTo>

</parameters>

</component>

信用卡的处理 Exercise:Credit Card Processing

If you’re feeling ready for a challenge, try this. Most e-commerce sites involve credit card processing, but almost

every implementation is different. The API varies according to which payment processing gateway you sign up

with. So, given this abstract service:

public interface ICreditCardProcessor

{

TransactionResult TakePayment(CreditCard card, decimal amount);

}

public class CreditCard

{

public string CardNumber { get; set; }

public string CardholderName { get; set; }

public string ExpiryDate { get; set; }

public string SecurityCode { get; set; }

}

public enum TransactionResult

{

Success, CardNumberInvalid, CardExpired, TransactionDeclined

}

can you enhance CartController to work with it? This will involve several steps:

• Updating CartController’s constructor to receive an ICreditCardProcessor instance.

• Updating /Views/Cart/CheckOut.aspx to prompt the customer for card details.

• Updating CartController’s POST-handling CheckOut action to send those card details to the

ICreditCardProcessor. If the transaction fails, you’ll need to display a suitable message and not

submit the order to IOrderSubmitter.

This underlines the strengths of component-oriented architecture and IoC. You can design, implement, and validate

CartController’s credit card–processing behavior with unit tests, without having to open a web browser and

without needing any concrete implementation of ICreditCardProcessor (just set up a mock instance). When

you want to run it in a browser, implement some kind of FakeCreditCardProcessor and attach it to your IoC

container using web.config. If you’re inclined, you can create one or more implementations that wrap real-world

credit card processor APIs, and switch between them just by editing your web.config file.

SportsStore管理员和最终增强

添加,删除,修改,查询操作

Form认证

文件上传

显示sql数据库中的图片

添加分类管理

创建AdminController,选中创建Create,update,insert select。自动生成代码:

public class AdminController : Controller

{

//

// GET: /Admin/

public ActionResult Index()

{

return View();

}

//

// GET: /Admin/Details/5

public ActionResult Details(int id)

{

return View();

}

//

// GET: /Admin/Create

public ActionResult Create()

{

return View();

} 

//

// POST: /Admin/Create

[HttpPost]

public ActionResult Create(FormCollection collection)

{

try

{

// TODO: Add insert logic here

return RedirectToAction("Index");

}

catch

{

return View();

}

}

//

// GET: /Admin/Edit/5

public ActionResult Edit(int id)

{

return View();

}

//

// POST: /Admin/Edit/5

[HttpPost]

public ActionResult Edit(int id, FormCollection collection)

{

try

{

// TODO: Add update logic here

return RedirectToAction("Index");

}

catch

{

return View();

}

}

}

添加构造函数:

private IProductsRepository productsRepository;

public AdminController(IProductsRepository productsRepository)

{

this.productsRepository = productsRepository;

}

[TestFixture]

class AdminControllerTests

{// Will share this same repository across all the AdminControllerTests

private Moq.Mock<IProductsRepository> mockRepos;

// This method gets called before each test is run

[SetUp]

public void SetUp()

{

// Make a new mock repository with 50 products

List<Product> allProducts = new List<Product>();

for (int i = 1; i <= 50; i++)

allProducts.Add(new Product { ProductID = i, Name = "Product " + i });

mockRepos = new Moq.Mock<IProductsRepository>();

mockRepos.Setup(x => x.Products)

.Returns(allProducts.AsQueryable());

}

[Test]

public void Index_Action_Lists_All_Products()

{

// Arrange

AdminController controller = new AdminController(mockRepos.Object);

// Act

ViewResult results = controller.Index();

// Assert: Renders default view

Assert.IsEmpty(results.ViewName);

// Assert: Check that all the products are included

var prodsRendered = (List<Product>)results.ViewData.Model;

Assert.AreEqual(50, prodsRendered.Count);

for (int i = 0; i < 50; i++)

Assert.AreEqual("Product " + (i + 1), prodsRendered[i].Name);

}

}

Rendering a Grid of Products in the Repository

为AdminController修改Index()

public ViewResult Index()

{

return View(productsRepository.Products.ToList());

}

测试通过。

声明一个ListView 模板。在Share文件夹,右键单击添加新项:

clip_image003

添加新的样式表:adminstyle.css

BODY, TD { font-family: Segoe UI, Verdana }
; padding-top: 0; font-weight: bold;

H1 { padding: .5em

font-size: 1.5em; border-bottom: 2px solid gray; }

DIV#content { padding: .9em; }

TABLE.Grid TD, TABLE.Grid TH { border-bottom: 1px dotted gray; text-align:left; }

TABLE.Grid { border-collapse: collapse; width:100%; }

TABLE.Grid TH.NumericCol, Table.Grid TD.NumericCol {

text-align: right; padding-right: 1em; }

DIV.Message { background: gray; color:White; padding: .2em; margin-top:.25em; }

.field-validation-error { color: red; }

.input-validation-error { border: 1px solid red; background-color: #ffeeee; }

.validation-summary-errors { font-weight: bold; color: red; }

现在可以为AdminControllerr的Index()添加新的View:

clip_image004

添加必要的css

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Admin.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<DomainModel.Entities.Product>>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">

All Products

</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

<h2>All Products</h2>

<table>

<tr>

<th>

ID

</th>

<th>

Name

</th>

<th class="NumericCol">

Price

</th>

<th>

Action

</th>

</tr>

<% foreach (var item in Model) { %>

<tr>

<td>

<%= Html.Encode(item.ProductID) %>

</td>

<td>

<%= Html.Encode(item.Name) %>

</td>

<td class="NumericCol">

<%= Html.Encode(String.Format("{0:F}", item.Price)) %>

</td>

<td>

<%= Html.ActionLink("Edit", "Edit", new { id=item.ProductID }) %> |

<%= Html.ActionLink("Details", "Details", new { id=item.ProductID })%>

</td>

</tr>

<% } %>

</table>

<p>

<%= Html.ActionLink("Add a new product", "Create") %>

</p>

</asp:Content>

> 

访问测试如下:

clip_image006

创建一个产品编辑

创建测试:

[Test]

public void Edit_Product()

{

// Arrange

AdminController controller = new AdminController(mockRepos.Object);

// Act

ViewResult result = controller.Edit(17);

Product renderedProduct = (Product)result.ViewData.Model;

Assert.AreEqual(17, renderedProduct.ProductID);

Assert.AreEqual("Product 17", renderedProduct.Name);

}

为AdminController添加GET Edit方法

public ViewResult Edit(int id)

{

Product product = (from p in productsRepository.Products

where p.ProductID == id

select p).First();

return View(product);

}

为Edit添加View。

clip_image007

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Admin.Master" Inherits="System.Web.Mvc.ViewPage<DomainModel.Entities.Product>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">

Admin : Edit <%=Model.Name %>

</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

<h2> Edit <%=Model.Name %></h2>

<% using (Html.BeginForm()) {%>

<fieldset>

<legend>Fields</legend>

<%=Html.Hidden("ProductID") %>

<p>

<%= Html.LabelFor(model => model.Name) %>

<%= Html.TextBoxFor(model => model.Name) %>

<%= Html.ValidationMessageFor(model => model.Name) %>

</p>

<p>

<%= Html.LabelFor(model => model.Description) %>

<%= Html.TextBoxFor(model => model.Description) %>

<%= Html.ValidationMessageFor(model => model.Description) %>

</p>

<p>

<%= Html.LabelFor(model => model.Price) %>

<%= Html.TextBoxFor(model => model.Price, String.Format("{0:F}", Model.Price)) %>

<%= Html.ValidationMessageFor(model => model.Price) %>

</p>

<p>

<%= Html.LabelFor(model => model.Category) %>

<%= Html.TextBoxFor(model => model.Category) %>

<%= Html.ValidationMessageFor(model => model.Category) %>

</p>

<p>

<input type="submit" value="Save" />

</p>

</fieldset>

<% } %>

<div>

<%=Html.ActionLink("Back to List", "Index") %>

</div>

</asp:Content>

处理Edit的提交。

创建测试:

[Test]

public void Edit_Sumbitting_Product_And_Redirects_To_Index()

{

// Arrange

AdminController controller = new AdminController(mockRepos.Object);

Product newProduct = new Product();

// Act

var result = (RedirectToRouteResult)controller.Edit(newProduct);

// Assert: Saved product to repository and redirected

mockRepos.Verify(x => x.SaveProduct(newProduct));

Assert.AreEqual("Index", result.RouteValues["action"]);

}

为IProductsRepository添加SaveProduct接口。

为SqlProductsRepository实现方法:

public void SaveProduct(Product product)

{

// If it's a new product, just attach it to the DataContext

if (product.ProductID == 0)

productsTable.InsertOnSubmit(product);

else

{

// If we're updating an existing product, tell the DataContext

// to be responsible for saving this instance

productsTable.Attach(product);

// Also tell the DataContext to detect any changes since the last save

productsTable.Context.Refresh(RefreshMode.KeepCurrentValues, product);

}

productsTable.Context.SubmitChanges();

}

修改Post方法的Edit Action。

[HttpPost]

public ActionResult Edit(Product product)

{

try

{

// TODO: Add update logic here

if (ModelState.IsValid)

{

productsRepository.SaveProduct(product);

TempData["message"] = product.Name + " has been saved.";

return RedirectToAction("Index");

}

else //Validation error, so redisplay save view

return View(product);

}

catch

{

return View(product);

}

}

编辑Admin.Master模板显示提示信息:

<% if (TempData["message"] != null)

{ %>

<div class="Message">

<%= Html.Encode(TempData["message"]) %></div>

<% } %>

F5运行测试.

clip_image009

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

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