做到购物车

作者:[美]Adam Freeman
     来源:《精通ASP.NET MVC
4》

正文将持续营造 SportsStore
示例应用程序。在上一章中,添加了对购物车的着力协理,现在打算改正并成功其效果。

1.应用模型绑定

 MVC 框架使用了3个叫作“模型绑定”的系统,以便通过 HTTP 请求来制造一些
C# 对象,指标是把它们作为参数值传递给动作方法。例如,MVC
处理表单的措施就是如此。框架会考察对象动作方法的参数,并用1个模型绑定器来获取表单中
input 成分的值,并把它们转换来同名的参数类型。

模型绑定器能够因而请求中可用的新闻来创设 C# 类型,那是 MVC
框架的核心本性之一。本节将开创三个自定义模型绑定器来盖上 CartController
类。

人人爱好使用 Cart 控制器中的会话状态个性来储存和管理 Cart
对象,但却不爱好它要动用的劳作情势。它不合乎本应用程序模型的其他部分,而那是基于动作方法参数的(因为动作方法参数的操作以模型为底蕴,而对话状态的操作供给设置键值对,两者的行事措施不同)。此外。除非模仿基类的
Session 参数,不然不能方便的对 CartController
类实行单元测试,而那意味须求效法 Controller
类(控制器的基类),以及别的一些不指望处理的事物。

为了缓解这一难点,我们打算成立一个自定义模型绑定器,以赢得包罗在对话数据中的
Cart
对象(注意,常规的模型绑定器能够直接处理请求中的数据来创制模型对象,那里开创自定义绑定器的目标是为着处理会话中的数据,手工业用会话数据创造Cart 对象)。然后,MVC 框架能够创建 Cart 对象,并把它们作为参数字传送递给
Controller 类的动作方法。

始建自定义模型绑定器

因此兑现 IModelBinder 接口,能够创制二个自定义模型绑定器。在
SportsStore.WebUI 项目中新建文件夹“Binders”,并新建类文件
CartModelBinder.cs ,代码如下:

 

using SportsStore.Domain.Entities;
using System.Web.Mvc;

namespace SportsStore.WebUI.Binders
{
    public class CartModelBinder : IModelBinder
    {
        private const string sessionKey = "Cart";
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            //通过会话获取 Cart
            Cart cart = (Cart)controllerContext.HttpContext.Session[sessionKey];
            //若会话中没有 Cart ,则创建一个
            if (cart == null)
            {
                cart = new Cart();
                controllerContext.HttpContext.Session[sessionKey] = cart;
            }
            //返回 cart
            return cart;
        }
    }
}

这一个 IModelBinder 接口定义了三个格局: BindModel
。所提供的七个参数使得创设域模型对象变成恐怕。 ControllerContext
对控制器类所享有的万事信息提供了走访,这一个音讯包蕴了客户端请求的底细。
ModelBindingContext
提供了须要确立的模子对象的音讯,以及使绑定更易于处理的工具。

对此本例的指标而言,所关心的是 ControllerContext 类,它兼具 HttpContext
属性,它又呼应地有3个 Session
属性,该属性能够获得和安装会话数据。通过读取会话数据的键值能够获得Cart,而在对话中还不曾 Cart 时,又有啥不可创制3个 Cart 。

供给告诉 MVC 框架,它能够选取 CartModelBinder 类来缔造 Cart
的实例。那亟需在 Global.asax 的 Application_Start 方法中开始展览注册:

using SportsStore.Domain.Entities;
using SportsStore.WebUI.Binders;
using SportsStore.WebUI.Infrastructure;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace SportsStore.WebUI
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());

            ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder());
        }
    }
}

于今得以创新 CartController 类,删除 GetCart
方法而借助于以往的模型绑定器,MVC 框架会自行地动用它,代码如下:

using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace SportsStore.WebUI.Controllers
{
    public class CartController : Controller
    {
        private IProductRepository repository;

        public CartController(IProductRepository repo)
        {
            repository = repo;
        }

        public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl)
        {
            Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
            if (product != null)
            {
                cart.AddItem(product, 1);
            }
            return RedirectToAction("Index", new { returnUrl });
        }

        public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl)
        {
            Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
            if (product != null)
            {
                cart.RemoveLine(product);
            }
            return RedirectToAction("Index", new { returnUrl });
        }

        public ViewResult Index(Cart cart, string returnUrl)
        {
            return View(new CartIndexViewModel
            {
                Cart = cart,
                ReturnUrl = returnUrl
            });
        }
    }
}

上边代码删除了 GetCart 方法,并对各类动作方法添加了 Cart 参数。当 MVC
框架接收到1个请求,比如,须要调用 AddToCart
方法时,会率先观看动作方法的参数,然后考察可用的绑定器列表,并盘算找到1个能够创立各种参数类型实例的绑定器。那会须求自定义绑定器创制一个Cart
对象,而那时候通过行使对话状态天性来形成的。通过自定义绑定器的暗中认可绑定器,MVC
框架能够成立一组调用动作方法所急需的参数,那让开发者能够重构控制器,以便在接收到请求时知道怎么样创造Cart 对象。

像这么使用自定义模型绑定器有多少个好处。第二个好处是把用来创设 Cart
与创立控制器的逻辑分离开来了,那让开发者能够修改存款和储蓄 Cart
对象,而不需求修改控制器。第一个便宜是任何利用 Cart
对象的支配器类,都能够简单地把这么些目的申明为动作方法参数,并能够使用自定义模型绑定器。第⑧个便宜是它亦可对
Cart 控制器进行单元测试,而不供给效法大批量的ASP.NET 通道。

 

2.实现购物车

前方早已介绍了自定义模型绑定器,现在到了添加多个新特色来形成购物车成效的时候了。第1性情况将允许客户删除购物车物品,首个特征将在页面包车型地铁顶部突显购物车的摘要。

 

2.1 删除购物车物品

皇冠直营现金网官方网,前边已经定义了控制器中的 RemoveFromCart
动作方法,由此,让客户删除物品只但是是在视图大校那么些艺术暴表露来的业务。本文打算在购物车摘要的每一行中添加三个“Remove”按钮来做那件事。对
Views/Cart/Index.cshtml 所做的改动如下:

    <tbody>
        @foreach (var line in Model.Cart.Lines)
        {
            <tr>
                <td class="aling_center">@line.Quantity</td>
                <td class="aling_left">@line.Product.Name</td>
                <td class="aling_right">@line.Product.Price.ToString("c")</td>
                <td class="aling_right">@((line.Quantity * line.Product.Price).ToString("c"))</td>
                <td>
                    @using (Html.BeginForm("RemoveFromCart", "Cart"))
                    {
                        @Html.Hidden("ProductId", line.Product.ProductID)
                        @Html.HiddenFor(x => x.ReturnUrl)
                        <input class="actionButtons" type="submit" value="Remove" />
                    }
                </td>
            </tr>
        }
    </tbody>

注:能够使用强类型的 Html.HiddenFor 辅助器方法,为 ReturnUrl
模型属性成立3个隐藏字段,但那亟需运用基于字符串的 Html.Hidden
协助器方法,对 ProductID 字段做同样的作业。
若是写成“Html.HiddenFor(x=>line.Product.ProductID)”,该辅助器方法便会渲染2个以“line.Product.ProductID”为名称的隐藏字段。该字段名与
CartController.RemoveFromCart
动作方法的参数名不合作,那会使暗许的模型绑定器不能工作,由此 MVC
框架便不可能调用此格局了。

运营应用程序,能够看出成效:

皇冠直营现金网官方网 1

 

2.2 添加购物车摘要

今昔早已实现了2个成效化的购物车,但将该购物车集成到解密的方法还存在贰个题材:只有经过查看购物车摘要显示屏,客户才能领略她们的购物车里有个别什么。而且,他们只可以通过把三个新的物品进入购物车,才能收看购物车的摘要荧屏。

为了消除这一标题,本节打算添加二个小部件,它集中购物车的始末,并能够由此点击来显示购物车内容。下边将应用与丰裕领航不见十三分相似的办法来成功这一做事——作为二个动作,把它的出口注入到
Razor 布局。

率先,须求对 CartController 类添加四个简约的艺术,代码如下:

        public PartialViewResult Summary(Cart cart)
        {
            return PartialView(cart);
        }

能够看看,那是多个大概的办法。它只须要渲染3个视图,以目前 Cart
(它是自定义模型绑定器获得的)作为视图数据。还亟需三个分部视图,它在对这么些Summary 方法调用做出响应时被渲染。添加对应的 Summary
视图像和文字件,代码如下:

@model SportsStore.Domain.Entities.Cart

<div id="cart">

        <b>Your cart:</b>
        @Model.Lines.Sum(x => x.Quantity) item(s),
        @Model.ComputeTotalValue().ToString("c")

    @Html.ActionLink("Checkout", "Index", "Cart",
    new { returnUrl = Request.Url.PathAndQuery }, null)
</div>

那是三个归纳的视图,它彰显了购物车的物料数、那几个物料的总耗费,以及把购物车内容突显给用户的1个链接。以往,已经定义了有
Summary 动作方法所重回的那么些视图,能够在 _Layout.cshtml
文件中蕴藏它的渲染结果,代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="~/Content/Site.css" type="text/css" rel="stylesheet" />
</head>
<body>
    <div id="header">
        @{Html.RenderAction("Summary", "Cart");}
        <div class="title">SPORTS STORE</div>
    </div>
    <div id="categories">
        @{Html.RenderAction("Menu", "Nav");}
    </div>
    <div id="content">
        @RenderBody()
    </div>
</body>
</html>

终极一步是加上一些 CSS规则,对该分部视图中的元素进行格式化。对
SportsStore.WebUI 项目中的 Site.css 文件添加样式文件如下:

div#cart {float:right;margin:.8em;color:silver;background-color:#555;padding:.5em .5em .5em 1em;}
div#cart a {text-decoration:none;padding:.4em 1em .4em 1em;line-height:2.1em;margin-left:.5em;background-color:#333;color:white;border:1px solid black;}

运行程序即能够看到效果,对购物车添加物品时,物品数以及总开销都会扩大,如下图所示:

皇冠直营现金网官方网 2

利用这几个附属类小部件,未来得以让客户驾驭本身的购物车中有怎样。那么些附属类小部件也展现的提供了2个结算离店的点子。从中再贰遍见到用
RenderAction 把三个动作方法所渲染的输出组合到贰个 Web
页面是多么简单。那是将应用程序成效分解成清晰可选拔模块的一种很好的技能。

 

3.递给订单

前些天到了展现 SportsStore
最终一个客户性子的时候了:结算并完毕订单的能力。上面将扩大域模型,以提供收集用户送货细节的援助,并累加贰个处理那几个细节的表征。

 

3.1 扩大域模型

在 SportsStore.Domain 项目标 Entities 文件夹中新建类 ShippingDetails
。那是用来代表客户送货细节的类,具体代码如下:

 

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; }
    }
}

上述代码应用了 System.ComponentModel.DataAnnotations
命名空间的辨证申明属性,正如在此之前文章 【MVC 4】1.首先个 MVC
应用程序
 所做的那么。

 

3.2 添加结算进程

本例的指标是到达一个程序节点,以此作为用户输入其送货细节并递交订单的入口。为此,供给在购物车摘要视图上添加三个“Checkout
now”按钮。修改 Views/Cart/Index.cshtml 文件,修改代码如下:

...
<p class="aling_center actionButtons">
    <a href="@Model.ReturnUrl">Continue shopping</a>
    @Html.ActionLink("Checkout now", "Checkout")
</p>
...

风流为修改部分,这一修改生成了3个链接,点击这么些链接时,调用 Cart
控制器的 Checkout 动作方法。展现效果如下:

皇冠直营现金网官方网 3

 

后天亟待在 CartController 类中定义那一个 Checkout 方法:

public ViewResult Checkout()
{
   return View(new ShippingDetails());
}

此 Checkout 方法重回暗许视图,并传递3个新的 ShippingDetails
对象作为视图模型。为了创立相应的视图像和文字件 Views/Cart/Checkout.cshtml
,视图内容如下:

@model SportsStore.Domain.Entities.ShippingDetails

@{
    ViewBag.Title = "SportsStore: Checkout";
}

<h2>Check out now</h2>
Please enter your details, and we'll ship your goods right away!
@using (Html.BeginForm())
{ 
    <h3>Ship to</h3>
    <div>Name:@Html.EditorFor(x => x.Name)</div>

    <h3>Address</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>City: @Html.EditorFor(x => x.City)</div>
    <div>State: @Html.EditorFor(x => x.State)</div>
    <div>Zip: @Html.EditorFor(x => x.Zip)</div>
    <div>Country: @Html.EditorFor(x => x.Country)</div>

    <h3>Options</h3>
    <label>
        @Html.EditorFor(x => x.GiftWrap)
        Gift wrap these items
    </label>

    <p class="align_center">
        <input class="actionButtons" type="submit" value="Complete order" />
    </p>
}

运维程序,可以观望该视图是什么样渲染的,效果如下图所示,该视图为搜集客户的送货细节渲染了3个表单。

皇冠直营现金网官方网 4

本例用 Html.艾德itorFor 扶助器方法为种种表单字段渲染了1个 input
成分。该格局是模板化帮忙器方法的二个例证(注意,对模型对象的各个属性使用的都是Html.艾德itorFor
援助器方法,并未针对各种属性的连串,去选定特定的帮衬器方法)。它让 MVC
框架去控制3个视图模型属性必要选拔哪类 input
成分,而不是开始展览精通的内定(例如,使用 Html.TextBoxFor)。

从出示效果能够看来, MVC 框架为布尔属性渲染了三个复选框,如“Gift wrap
these items”选用,而对那二个字符串属性渲染了文本框。

 

3.3 完结订单处理器

 在这几个应用程序中还亟需叁个零部件,以便能够对订单的底细举办处理。为了与
MVC
模型原理保持一致,本例打算为此成效定义2个假说、编写该接口的2个兑现,然后用
DI 容器 Ninject 把双方关系起来。

 

概念接口

在 SportsStore.Domain 项目标 Abstract 文件夹中新建接口 IOrderProcessor
,内容如下:

using SportsStore.Domain.Entities;

namespace SportsStore.Domain.Abstract
{
    public interface IOrderProcessor
    {
        void ProcessOrder(Cart cart, ShippingDetails shippingDetails);
    }
}

 

贯彻接口

IOrderProcessor
的兑现打算动用的订单处理格局是向网站管理员发送订单右击。当然,那简化了销售经过。一大半电子商务网站不会简单的发送订单邮件,而且也尚未提供信用卡处理或其余支付形式的扶助,只是希望把业务维持在关怀MVC 方面,因此选取了那种发送邮件作为订单处理的方法。

在 SportsStore.Domain 项指标 Concrete 文件夹中新建类
EmailOrderProcessor,那么些类应用了富含在 .NET 框架中内建的
SMTP(不难邮件传输协议)援助,以发送一份电子邮件。具体代码如下:

 

using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System.Net;
using System.Net.Mail;
using System.Text;
namespace SportsStore.Domain.Concrete
{
    public class EmailSettings
    {
        public string MailToAddress = "orders@example.com";
        public string MailFromAddress = "sportsstore@example.com";
        public bool UserSsl = 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.UserSsl;
                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,//Form
                    emailSettings.MailToAddress,//To
                    "New order submitted!",//Subject
                    body.ToString());//Body

                if (emailSettings.WriteAsFile)
                {
                    mailmessage.BodyEncoding = Encoding.ASCII;
                }
                smtpClient.Send(mailmessage);
            }
        }
    }
}

为了使工作更简单些,也定义了 EmailSettings 类。 EmailOrderProcessor
的构造器需求以此类(EmailSettings 类)的一个实例,该实例包蕴了配置 .NET
邮件类所急需的全数安装新闻。

提示:若果没有可用的 SMTP 服务器也没涉及,能够将
EmailSettings.WriteAsFile属性设置为true,那样会把邮件音讯作为文件写到由
FileLocation
属性钦命的目录。该目录必须依照存在且是可写入的。邮件文件的扩充宿将为
.eml ,但它们能够被别的公文编辑器所读取。

 

3.4 注册(接口)实现

明天,有了 IOrderProcessor 接口的二个兑现以及配备它的手段,便能够用
Ninject 来创立它的实例。编辑 SportsStore.WebUI 项目中的
NinjectController 类(在 Infrastructure 文件夹中),对 AddBindings
方法实行修改:

using Moq;
using Ninject;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
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()
        {
            //put bindings here
            ninjectKernel.Bind<IProductRepository>().To<EFProductRepository>();

            EmailSettings emailSettings = new EmailSettings
            {
                WriteAsFile = bool.Parse(ConfigurationManager.AppSettings["Email.WriteAsFile"] ?? "false")
            };
            ninjectKernel.Bind<IOrderProcessor>().To<EmailOrderProcessor>().WithConstructorArgument("settings", emailSettings);
        }
    }
}

上述代码创立了3个 EmailSettings 对象,将其用于 Ninject 的
WithConstructorArgument 方法,以便在要求创造贰个新实例对 IOrderProcessor
接口的伸手举办劳动时,把它注入到 EmailOrderProcessor
构造器中。上述代码只为 EmailSettings 中叁本质量 WriteAsFiles
钦定了值。使用 ConfigurationManager.AppSettings
属性来读取该属性的值,那让用户能够访问已经位于 Web.config
文件中的应用程序,在应用程序配置文件 Web.config 中读取那么些 WriteAsFile
属性的装置值,那也是从应用程序配置文件 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>

 

3.5 完结购物车控制器

为了完毕 CartController 类,必要修改构造器,以使它要求 IOrderProcessor
接口的二个兑现,并足够三个新的动作方法,它将在客户点击“Complete
order”按钮时,处理 HTTP 表单的 POST 请求。

using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace SportsStore.WebUI.Controllers
{
    public class CartController : Controller
    {
        private IProductRepository repository;
        private IOrderProcessor orderProcessor;

        public CartController(IProductRepository repo, IOrderProcessor proc)
        {
            repository = repo;
            orderProcessor = proc;
        }

        public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl)
        {
            Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
            if (product != null)
            {
                cart.AddItem(product, 1);
            }
            return RedirectToAction("Index", new { returnUrl });
        }

        public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl)
        {
            Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
            if (product != null)
            {
                cart.RemoveLine(product);
            }
            return RedirectToAction("Index", new { returnUrl });
        }

        public ViewResult Index(Cart cart, string returnUrl)
        {
            return View(new CartIndexViewModel
            {
                Cart = cart,
                ReturnUrl = returnUrl
            });
        }

        public PartialViewResult Summary(Cart cart)
        {
            return PartialView(cart);
        }


        [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);
            }
        }

        public ViewResult Checkout()
        {
            return View(new ShippingDetails());
        }
    }
}

 

能够看出,那里所添加的 Checkout 动作方法是用 HttpPost
注解属性来修饰的,那评释该情势将用来对 POST 请求的拍卖 ——
在此例中,那是用户递交表单的时候。再一次重复,ShippingDetails
参数(那是采纳 HTTP 表单数据自动创立的)和 Cart
参数(那是用自定义绑定器创设的)都要凭借于模型绑定器系统。

注:构造器中的修改迫使用户须求对 CartController
类创造的单元测试举办翻新。为新的构造器参数字传送递 null
,便会使单元测试能够由此编写翻译。

在上述代码中, MVC 框架会检查认证约束,那一个约束是用数码注明属性而使用于
ShippingDetails 的,并经过 ModelState
属性把地下情况传递给该动作方法。由此,能够透过检查 Model.IsValid
属性来查阅是还是不是留存难点。注意,固然购物车中无物品,还调用
ModelState.AddModelError 方法注册了一条错误消息。

 

3.6 展现验证错误

借使客户输入了违规的错误信息,有极度态的那多少个非法表单字段将被高亮,但绝非音信被显示出来。更糟的是,若是客户准备对贰个空购物车举办结算,那不会形成那么些订单,但客户却常有看不到任何错误消息。为了缓解那一个题材,要求对视图添加1个注明摘要。下边代码展现了丰盛到
Checkout.cshtml 视图的剧情。

...
<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(x => x.Name)</div>
...

当今,当客户提供违规送货数据或打算对空购物车进行结算时,系统会向她们展现一些卓有功效的荒谬音信,如下图所示:

皇冠直营现金网官方网 5

 

3.7 展现致谢界面

为了做到结算进度,须求向客户出示八个业已成功订单处理的认同页面,并致谢她们的购物。新建视图像和文字件
Views/Cart/Completed.cshtml ,代码如下:

@{
    ViewBag.Title = "SportsStore: OrderSubmitted";
}

<h2>Thanks!</h2>
Thanks for placing your order. We'll ship your goods as soon as possible.

今昔,客户能够拓展从选择产品到结算离开的任何购物进度。假若客户提供立竿见影的的送货细节(且购物车中有物品),当他们点击“Complete
order”按钮时,便会看出二个多谢页面,如下图所示:

皇冠直营现金网官方网 6

皇冠直营现金网官方网 7

相关文章