實在不好意思,好久沒有更新了,我不想找些客觀原因來解釋,只想請大家見諒!現在我們繼續我們的項目,客戶已經完成了訂單的確認,但我們還沒有一個地方可以讓客戶輸入他們的收貨信息,我們的商品沒辦法發貨,這是個嚴重的問題,我們必須解決它。現在,我們就在SportsStore.Domain工程的Entities文件夾中添加一個ShippingDetails類,在這個類中,我們使用了System.ComponentModel.DataAnnotations命名空間,去驗證客戶的輸入:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; 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 line")] public string Line1 { get; set; } public string Line2 { get; set; } public string Line3 { 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; } } }
我們的目的是讓用戶輸入收貨的詳細信息后能夠去付款,畢竟要賺錢嗎,這個沒啥不好意思的,我們這就去修改一下我們的summary視圖,打開Views/Cart/Index.cshtml文件,我們要在這添加一個支付按鈕,修改文件的最后部分像下面的樣子,然后,運行一下你的代碼,看看效果:)
</table> <p align="center" class="actionButtons"> <a href="@Model.ReturnUrl">繼續購物</a> @Html.ActionLink("支付", "Checkout") </p>
正如你所預見的,現在我們要為CartController類添加一個Checkout Action方法:
public ViewResult Checkout() { return View(new ShippingDetails()); }
Checkout方法返回一個默認的view,並傳遞一個new ShippingDetails對象作為view model. 現在我們就去創建一個ShippingDetails類型的強視圖:
修改視圖代碼如下:
@model SportsStore.Domain.Entities.ShippingDetails @{ ViewBag.Title = "SportStore: Checkout"; } <h2>現在支付</h2> 請輸入你的詳細信息, 我們會根據您的信息發貨! @using (Html.BeginForm()) { <h3>發貨到</h3> <div>姓名: @Html.EditorFor(x => x.Name)</div> <h3>地址</h3> <div>Line 1: @Html.EditorFor(x => x.Line1)</div> <div>Line 2: @Html.EditorFor(x => x.Line2)</div> <div>Line 3: @Html.EditorFor(x => x.Line3)</div> <div>城市: @Html.EditorFor(x => x.City)</div> <div>區: @Html.EditorFor(x => x.State)</div> <div>郵編: @Html.EditorFor(x => x.Zip)</div> <div>國家: @Html.EditorFor(x => x.Country)</div> <h3>可選項</h3> <label> @Html.EditorFor(x => x.GiftWrap) 作為禮品包裝我的商品 </label> <p align="center"> <input class="actionButtons" type="submit" value="完成訂單" /> </p> }
實現訂單處理器
我們需要一個組件,通過這個組件,我們能夠容易的把握訂單的處理流程,為了保持遵守MVC模型的基本原則,我們要定義一個接口,寫一個這個接口的實現類,使我們的DI容器和 Ninject和這個實現類整合在一起.
添加一個IOrderProcessor接口到SportsStore.Domain工程的Abstract文件夾:
using SportsStore.Domain.Entities; namespace SportsStore.Domain.Abstract { public interface IOrderProcessor { void ProcessOrder(Cart cart, ShippingDetails shippingDetails); } }
實現接口
IOrderProcessor接口的實現類將通過發送額email給管理員處理訂單,當然了,我們簡化了這個流程,真正的大型商務網站不只是發郵件這么簡單! 現在我們要創建一個新類,叫做 EmailOrderProcessor,把它放在SportsStore.Domain工程的Concrete文件夾中,這個類我們使用.NET Framework library內建的SMTP去發送郵件:
using System.Net.Mail; using System.Text; using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using System.Net; namespace SportsStore.Domain.Concrete { public class EmailSettings { public string MailToAddress = "orders@example.com"; public string MailFromAddress = "sportsstore@example.com"; public bool UseSsl = true; public string Username = "MySmtpUsername"; public string Password = "MySmtpPassword"; public string ServerName = "smtp.example.com"; public int ServerPort = 587; public bool WriteAsFile = false; public string FileLocation = @"c:\sports_store_emails"; } public class EmailOrderProcessor :IOrderProcessor { private EmailSettings emailSettings; public EmailOrderProcessor(EmailSettings settings) { emailSettings = settings; } public void ProcessOrder(Cart cart, ShippingDetails shippingInfo) { using (var smtpClient = new SmtpClient()) { smtpClient.EnableSsl = emailSettings.UseSsl; smtpClient.Host = emailSettings.ServerName; smtpClient.Port = emailSettings.ServerPort; smtpClient.UseDefaultCredentials = false; smtpClient.Credentials = new NetworkCredential(emailSettings.Username, emailSettings.Password); if (emailSettings.WriteAsFile) { smtpClient.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory; smtpClient.PickupDirectoryLocation = emailSettings.FileLocation; smtpClient.EnableSsl = false; } StringBuilder body = new StringBuilder().AppendLine("A new order has been submitted") .AppendLine("---").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()).AppendLine("---") .AppendLine("Ship to:").AppendLine(shippingInfo.Name).AppendLine(shippingInfo.Line1) .AppendLine(shippingInfo.Line2 ?? "") .AppendLine(shippingInfo.Line3 ?? "") .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(emailSettings.MailFromAddress, // From emailSettings.MailToAddress, // To "New order submitted!", // Subject body.ToString()); // Body if (emailSettings.WriteAsFile) { mailMessage.BodyEncoding = Encoding.ASCII; } smtpClient.Send(mailMessage); } } } }
注冊實現類
為了讓Ninject能夠創建 IOrderProcessor接口的實現類,我們必須添加一些代碼到SportsStore.WebUI 工程的NinjectControllerFactory 類的AddBindings 方法,到這個方法里添加代碼,不用我說你也知道要干什么事了,馬上動手吧!
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using Moq; using Ninject; using SportsStore.Domain.Concrete; using System.Configuration; namespace SportsStore.WebUI.Infrastructure { public class NinjectControllerFactory: DefaultControllerFactory { private IKernel ninjectKernel; public NinjectControllerFactory() { ninjectKernel = new StandardKernel(); AddBindings(); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType); } private void AddBindings() { Mock<IProductsRepository> mock = new Mock<IProductsRepository>(); //mock.Setup(m => m.Products).Returns(new List<Product> { // new Product { Name = "Football", Price = 25 }, // new Product { Name = "Surf board", Price = 179 }, // new Product { Name = "Running shoes", Price = 95 } //}.AsQueryable()); //ninjectKernel.Bind<IProductsRepository>().ToConstant(mock.Object); ninjectKernel.Bind<IProductsRepository>().To<EFProductRepository>(); EmailSettings emailSettings = new EmailSettings { WriteAsFile = bool.Parse(ConfigurationManager.AppSettings["Email.WriteAsFile"] ?? "false") }; ninjectKernel.Bind<IOrderProcessor>().To<EmailOrderProcessor>().WithConstructorArgument("settings", emailSettings); } } }
我們創建了一個EmailSettings 對象, 當一個服務請求要求創建一個新的IOrderProcessor 接口實力的時候,我們使用Ninject的WithConstructorArgument 方法注入它到EmailOrderProcessor構造函數,因為我們使用了ConfigurationManager.AppSettings 屬性去訪問 Web.config文件,所以,我們要將一些配置添加到Web.config 文件中:
<appSettings> <add key="webpages:Version" value="2.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="PreserveLoginUrl" value="true" /> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> <add key="Email.WriteAsFile" value="true"/> </appSettings>
完善Cart Controller
我們要修改CartController類,讓它的構造函數去要求一個IOrderProcessor接口的實現,並添加一個新的方法處理用戶點擊完成訂單按鈕時,post過來的Http請求:
private IOrderProcessor orderProcessor; public CartController(IProductsRepository repo, IOrderProcessor proc) { repository = repo; orderProcessor = proc; }
[HttpPost] public ViewResult Checkout(Cart cart, ShippingDetails shippingDetails) { if (cart.Lines.Count() == 0) { ModelState.AddModelError("", "Sorry, your cart is empty!"); } if (ModelState.IsValid) { orderProcessor.ProcessOrder(cart, shippingDetails); cart.Clear(); return View("Completed"); } else { return View(shippingDetails); } }
你現在看到了,我們添加的Checkout方法帶有一個HttpPost 屬性,這意味着它將為一個post請求調用,當用戶提交一個表單時,我們將依賴MVC的model binder system, ShippingDetails 參數和Cart 參數創建我們的model binder。
為了展示用戶的輸入錯誤,我們需要在Checkout view中添加 @Html.ValidationSummary() 標記,看起來應該像下面的樣子:
<h2>現在支付</h2> 請輸入你的詳細信息, 我們會根據您的信息發貨! @using (Html.BeginForm()) { @Html.ValidationSummary() <h3>發貨到</h3>……
展示Summary頁
為了完善支付流程, 我們應該顯示一個訂單已經被處理的確認頁給用戶,右擊CartController類的任意方法去添加一個視圖,命名為Completed,這個視圖我們不需要定義為強類型:
@{ ViewBag.Title = "SportsStore: Order Submitted"; } <h2>謝謝!</h2> 感謝您購買我們的商品. 我們將盡可能快的發送貨物給您.
運行你的程序前,別忘了修改你的email賬戶和密碼,還有C盤下要建一個c:\sports_store_emails文件夾哦!在下一篇中,我們將為我們的網站創建一個CRUD的管理后台,這是所有網站都比不可少的功能,我們當然也不會少了!感謝您的關注!如果有任何問題請在我的博客上留言,我會盡可能詳盡的為您解答,下篇再見!