ASP.NET MVC SportStore 购物网示例(6)
2011-03-24 21:03
357 查看
定义一个订单提供 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视图。
使用 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 almostevery 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文件夹,右键单击添加新项:
添加新的样式表: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:
添加必要的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>
>
访问测试如下:
创建一个产品编辑
创建测试:
[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。
<%@ 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运行测试.
转载请注明出处! Author: im@xingquan.org
相关文章推荐
- ASP.NET MVC SportStore 购物网示例(3)
- ASP.NET MVC SportStore 购物网示例(4)
- ASP.NET MVC SportStore 购物网示例(5)
- ASP.NET MVC SportStore 购物网示例(7)
- ASP.NET MVC SportStore 购物网示例(1)
- ASP.NET MVC SportStore 购物网示例(2)
- ASP.NET MVC:窗体身份验证及角色权限管理示例
- ASP.NET MVC 企业级实战 —— 创建用户权限管理示例程序(一)
- 转发:ASP.NET MVC 4与Windows Azure 表、Blobs、队列的教程系列和示例应用程序
- 《ASP.NET MVC Music Store Tutorial》在Visual Studio 2008简体中文环境下的代码修订
- 《Pro ASP.NET MVC 3 Framework》学习笔记之十二【示例项目SportsStore及MyBatis.NET的使用】
- Asp.Net MVC 第一个入门示例
- ASP.NET Mvc + NHibernate + Unity Application Block 示例程序
- ASP.NET MVC 窗体身份验证及角色权限管理示例
- ASP.NET MVC Music Store教程(3):视图和视图模型
- ASP.NET MVC中使用JavaScriptResult的用法示例
- 转发:ASP.NET MVC 4与Windows Azure 表、Blobs、队列的教程系列和示例应用程序
- Asp.net MVC 示例项目"Suteki.Shop"分析之---ViewData
- 【转载】Spring.net与Asp.net Mvc结合入门示例
- ASP.NET Mvc + NHibernate + Unity Application Block 示例程序(转)