使用MVC4,Ninject,EF,Moq,構建一個真實的應用電子商務SportsStore(九)


實在不好意思,好久沒有更新了,我不想找些客觀原因來解釋,只想請大家見諒!現在我們繼續我們的項目,客戶已經完成了訂單的確認,但我們還沒有一個地方可以讓客戶輸入他們的收貨信息,我們的商品沒辦法發貨,這是個嚴重的問題,我們必須解決它。現在,我們就在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類型的強視圖:

image

修改視圖代碼如下:

 

@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,這個視圖我們不需要定義為強類型:

image

@{     ViewBag.Title = "SportsStore: Order Submitted"; }  
<h2>謝謝!</h2>
 感謝您購買我們的商品. 我們將盡可能快的發送貨物給您.

 

運行你的程序前,別忘了修改你的email賬戶和密碼,還有C盤下要建一個c:\sports_store_emails文件夾哦!在下一篇中,我們將為我們的網站創建一個CRUD的管理后台,這是所有網站都比不可少的功能,我們當然也不會少了!感謝您的關注!如果有任何問題請在我的博客上留言,我會盡可能詳盡的為您解答,下篇再見!

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM