随着我们购物车的不断完善,我们简单的完成到最后的订单模块。我们需要一个扩展的模型,在我们的域模型类库里,添加一个类(ShippingDetail)类,它的具体代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel.DataAnnotations; namespace SportsStore.Domain.Entities { public class ShippingDetails { [Required(ErrorMessage="Please enter a name")] public string Name { get; set; } [Required(ErrorMessage="Please enter the first Address")] public string Address1 { get; set; } public string Address2 { get; set; } public string Address3 { get; set; } [Required(ErrorMessage="Please enter a city name")] public string City { get; set; } [Required(ErrorMessage="Please enter a state name")] public string State { get; set; } public string Zip { get; set; } [Required(ErrorMessage="Please enter a country name")] public string Country { get; set; } public bool GiftWrap { get; set; } } }
接下来需要完善我们结算功能,我们需要添加一个结算按钮,在我们Web项目的Views/Cart/Index.cshtml修改如下(实际就添加了一个按钮):
@model SportsStore.WebUI.Models.CartIndexViewModel @{ ViewBag.Title = "Cart Index"; } <h2>Your Cart</h2> <table width="90%" align="center"> <thead> <tr> <th align="center">Qunantity</th> <th align="left">Item</th> <th align="right">Price</th> <th align="right">Subtotal</th> </tr> </thead> <tbody> @foreach (var line in Model.Cart.Lines) { <tr> <td align="center">@line.Quantity</td> <td align="left">@line.Product.Name</td> <td align="right">@line.Product.Price.ToString("c")</td> <td align="right">@((line.Quantity*line.Product.Price).ToString("c"))</td> <td> @using (Html.BeginForm("RemoveFromCart","Cart")) { @Html.Hidden("ProductId",line.Product.ProductID) @Html.HiddenFor(h=>h.ReturnUrl) <input class="actionButtons" type="submit" value="Remove" /> } </td> </tr> } </tbody> <tfoot> <tr> <td colspan="3">Total:</td> <td align="right">@Model.Cart.ComputeTotalValue().ToString("C")</td> </tr> </tfoot> </table> <p align="center" class="actionButtons"><a href="@Model.ReturnUrl">Continue shopping</a> @Html.ActionLink("Checkout now", "Checkout") </p>
上面红色部分的代码会呈现出一个带有支付连接的按钮,运行我们的项目如下图1.
图1.
接着我们需要建立一个视图(Checkout),在Cartcontroller控制器里添加一个返回视图(Checkout)的Action(方法),具体代码如下:
public ViewResult Checkout() { return this.View(new ShippingDetails()); }
添加完Action(方法)后,然后就需要添加视图(Checkout),右键选择添加视图,如下图2.
图2.这里还是选择强类型视图,因我我们需要使用之前我们定义的ShippingDeatails类。创建好视图(Checkout)后,修改他的内容如下:
@model SportsStore.Domain.Entities.ShippingDetails @{ ViewBag.Title = "SportStore:Checkout"; } <h2>Check out now</h2> Please enter your details,and we'll ship your goods right away! @using (Html.BeginForm()) { @Html.ValidationSummary() <h3>Ship to</h3> <div>Name: @Html.EditorFor(h=>h.Name)</div> <h3>Address</h3> <div>Address1:@Html.EditorFor(h=>h.Address1)</div> <div>Address2:@Html.EditorFor(h=>h.Address2)</div> <div>Address3:@Html.EditorFor(h=>h.Address3)</div> <div>City:@Html.EditorFor(h=>h.City)</div> <div>State:@Html.EditorFor(h=>h.State)</div> <div>Zip:@Html.EditorFor(h=>h.Zip)</div> <div>Country:@Html.EditorFor(h=>h.Country)</div> <h3>Options</h3> <label> @Html.EditorFor(h=>h.GiftWrap) GIft wrap these items </label> <p align="center"> <input class="actionButtons" type="submit" value="Complete order" /> </p> }
搞完视图运行我们的Web项目,结果如下图3
图3.使用Html.EditorFor辅助方法为每一个表单字段呈现input元素,我们让MVC框架能够算出view model属性需要哪一种input元素,而不是显示的指定。
接下我们实现订单处理器我们需要一个组件来处理订单的详情,为了保持MVC模型的严则,首先定义一个接口,并实现该接口。然后使用我们的DI容器--Ninject,在我们的Domain模型域类库项目里的Abstract文件夹里面定义一个接口(IOrderProcessor),具体代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using SportsStore.Domain.Entities; namespace SportsStore.Domain.Abstract { public interface IOrderProcessor { void ProcessOrder(Cart cart, ShippingDetails shippingDetails); } }
然后我们需要实现IOrderProcessor接口去处理客户提交的订单,如果一切OK的话,我们需要给用户发一份Email邮件告知用户。当然这里已经把购物流程简化的不像样子了,真正的流程这里应该是和银行(第三方)交互,等待支付成功后需要发邮件给用户,这里就简单的实现下。在我们的SportsStore.Domain类库下的Concrete文件夹下创建EmailOrderProcessor类,在这个类里使用.Net内置的SMTP实现发送电子邮件,具体代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Net.Mail; using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; namespace SportsStore.Domain.Concrete { public class EmailSettings { public string MailToAddress = "**************"; //自己试着配置 public string MailFromAddress = "*********@qq.com"; //自己试着配置 public bool UseSsl = false; public string Username = "********@qq.com"; //自己试着配置 public string Password = "*************"; //自己试着配置 用QQ的话这里要配置密码 public string ServerName = "Smtp.qq.com"; //pop.qq.com Smtp.qq.com mail.qq.com public int ServerPort = 25; public bool WriteAsFile = false; public string FileLocation = @"E:\work\SportsStore\sports_store_emails"; } public class EmailOrderProcessor : IOrderProcessor { private EmailSettings emailSetings; public EmailOrderProcessor(EmailSettings settings) { this.emailSetings = settings; } public void ProcessOrder(Cart cart, ShippingDetails shippingInfo) { using (var smtpClient = new SmtpClient()) { smtpClient.EnableSsl = this.emailSetings.UseSsl; smtpClient.Host = this.emailSetings.ServerName; smtpClient.Port = this.emailSetings.ServerPort; smtpClient.UseDefaultCredentials = false; smtpClient.Credentials = new NetworkCredential(this.emailSetings.Username, this.emailSetings.Password); if (this.emailSetings.WriteAsFile) { smtpClient.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory; smtpClient.PickupDirectoryLocation = this.emailSetings.FileLocation; smtpClient.EnableSsl = false; } StringBuilder body = new StringBuilder().AppendLine("A New Order Has Been Submitted") .AppendLine("---") .AppendLine("Items:"); foreach (var item in cart.Lines) { var subtotal = item.Product.Price * item.Quantity; body.AppendFormat("{0} × {1}(Subtotal:{2:c})", item.Quantity, item.Product.Name, subtotal); } body.AppendFormat("Total order Value:{0:c}", cart.ComputeTotalValue()) .AppendLine("---") .AppendLine("Ship to:") .AppendLine(shippingInfo.Name) .AppendLine(shippingInfo.Address1) .AppendLine(shippingInfo.Address2 ?? "") .AppendLine(shippingInfo.Address3 ?? "") .AppendLine(shippingInfo.City) .AppendLine(shippingInfo.State ?? "") .AppendLine(shippingInfo.Country) .AppendLine(shippingInfo.Zip) .AppendLine("---") .AppendFormat("GIft wrap:{0}", shippingInfo.GiftWrap ? "Yes" : "No"); MailMessage mailMessage = new MailMessage( this.emailSetings.MailFromAddress, //From this.emailSetings.MailToAddress, //To "New Order Submitted!", //Subject body.ToString()); if (this.emailSetings.WriteAsFile) { mailMessage.BodyEncoding = Encoding.ASCII; } smtpClient.Send(mailMessage); } } } }
现在,我们已经实现IOrderProcessor接口,我们可以使用Ninject创建实例方法配置它。在我们之前Web项目的Infrastructure文件夹下的NinjectControllerFactory类,具体代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Ninject; using System.Web.Routing; using Moq; using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using SportsStore.Domain.Concrete; using System.Configuration; namespace SportsStore.WebUI.Infrastructure { public class NinjectControllerFactory : DefaultControllerFactory { private IKernel ninjectKernel; public NinjectControllerFactory() { this.ninjectKernel = new StandardKernel(); AddBindings(); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType); } private void AddBindings() { //绑定额外数据 this.ninjectKernel.Bind<IProductRepository>().To<EFProductRepository>(); EmailSettings emailSettings = new EmailSettings { WriteAsFile = bool.Parse(ConfigurationManager.AppSettings["Email.WriteAsFile"] ?? "false") }; this.ninjectKernel.Bind<IOrderProcessor>().To<EmailOrderProcessor>() .WithConstructorArgument("settings", emailSettings); } } }
这里的Email.WriteAsFile在配置文件里面配置的,主要是考虑是在没有smtp服务器的情况下,将邮件复制到指定目录。其实一般的邮箱都开通了smtp服务的,所以我们将这里的默认值设为false。在Web.config里面配置<add key="Email.WriteAsFile" value="false"/>,具体如下:Web项目的Views文件下Web.config如下:
<appSettings> <add key="webpages:Enabled" value="false" /> <add key="Email.WriteAsFile" value="true" /> </appSettings>
我们现在需要完善我们Cartcontroller,我们需要一个构造函数在用户单击支付按钮的时候,他需要实现IOrderProcessor接口,然后发送邮件,具体修改CartController控制器代码如下:
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 CartController : Controller { private IProductRepository repository; private IOrderProcessor orderProcessor; public CartController(IProductRepository repo,IOrderProcessor proc) { this.repository = repo; this.orderProcessor = proc; } //添加购物车 public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl) { Product product = this.repository.Products.FirstOrDefault(h => h.ProductID == productId); if (product != null) { cart.AddItem(product, 1); } return this.RedirectToAction("Index", new { returnUrl }); } //移除商品 public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl) { Product product = this.repository.Products.FirstOrDefault(h => h.ProductID == productId); if (product != null) { cart.RemoveLine(product); } return this.RedirectToAction("Index", new { cart = cart, returnUrl = returnUrl }); } private Cart GetCart() { Cart cart = (Cart)Session["Cart"]; if (cart == null) { cart = new Cart(); this.Session["Cart"] = cart; } return cart; } public ViewResult Index(Cart cart, string returnUrl) { return this.View(new CartIndexViewModel { Cart = cart, ReturnUrl = returnUrl }); } //简易的购物车总结 public ViewResult Summary(Cart cart) { return this.View(cart); } //结算的方法 [HttpPost] public ViewResult Checkout(Cart cart, ShippingDetails shippingDetails) { if (cart.Lines.Count() == 0) { ModelState.AddModelError("", "Sorry,your cart is empty!"); } if (ModelState.IsValid) { this.orderProcessor.ProcessOrder(cart, shippingDetails); cart.Clear(); return this.View("Completed"); } else { return this.View(shippingDetails); } } public ViewResult Checkout() { return this.View(new ShippingDetails()); } } }
然后我们需要添加一个视图页面"Completed",他表示我们已经支付成功,返回友好的信息,具体如下图。
这里我们不要想在选择强类型视图,因为我们使用它值呈现一些简单的东西,所以我们还要使用模版(_Layout)我希望他们的风格还是一样的,具体代码如下:
@{ ViewBag.Title = "SportsStore:Order Completed"; } <h2>Thanks!</h2> Thanks for placing you order.We'll ship your goods as soon as possible
添加完成后运行我们的项目如下图4(展示我们验证不通过)-图5(购物成功)。
图4.
图5.
项目就简单的搞到这里,我们需要一个简单后续在补上一个简单的后台(简单项目肯定需要一个简单的后台)来管理我们项目。几天我们简单的购物流程就到这里