【MVC 4】电子商务7.SportsSore:完成购物车

 

电子商务,上述代码应用了 System.ComponentModel.DataAnnotations
命名空间的验证阐明属性,正如后边作品 【MVC 4】1.率先个 MVC
应用程序
 所做的这样。

需要修改 URL 的端口号,使之与正在运作的 ASP.NET
开发服务器端口号匹配。运用这种查询字符串,可以对全部产品分类举办导航。

在 SportsStore.Domain 项指标 Concrete 文件夹中新建类
EmailOrderProcessor,那多少个类使用了富含在 .NET 框架中内建的
SMTP(简单邮件传输协议)匡助,以发送一份电子邮件。具体代码如下:

【MVC 4】2.使用
Razor
中曾讲演了
Razor 布局是何等工作和使用的。 当为 Product 控制器创设 List.cshtml
视图时,曾要求用户选中 “使用一个搭架子”
复选框,但该文本框保留为空。这便利用了默认布局 _Layout.cshtml,可以在
SportsStore.WebUI 项目标 Views/Shared
文件夹中找到它。打开这一个文件并修改如下:

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

4.预备数据库

@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>

点击”更新数据库”,以执行该 SQL 语句,并在数据库中创造 Products 表。

从突显效果可以见见, MVC 框架为布尔属性渲染了一个复选框,如“Gift wrap
these items”采用,而对这多少个字符串属性渲染了文本框。

出于这是一个电子商务应用程序,因而需要的最分明的域实体是成品(Product)。在SportsSore.Domain
项目中创制一个名为 “Entities”
的新文件夹,然后在里边成立一个名为“Product”的类。

此 Checkout 方法重临默认视图,并传递一个新的 ShippingDetails
对象作为视图模型。为了创制相应的视图文件 Views/Cart/Checkout.cshtml
,视图内容如下:

前些天一度完结好了再 List
视图上添加页面链接的有所准备。前边已经创办了蕴藏分页信息的视图模型,更新了控制器以使这个音讯可以传递给视图,并修改了
@model 指示符以匹配新的视图模型类。剩下的事是在视图中调用这些 HTML
帮助器方法,修改视图文件如下:

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

电子商务 1

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

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

namespace SportsStore.WebUI
{
    // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明,
    // 请访问 http://go.microsoft.com/?LinkId=9394801

    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());
        }
    }
}
public ViewResult Checkout()
{
   return View(new ShippingDetails());
}

在此打算建立的应用程序 — SportsStore
(体育用品商店),将按部就班随处可见的在线商店所拔取的经文格局。将创建一个客户可以因此分类和页面举办浏览的在线产品分类,一个客户可以加上和删除商品的购物车,和一个客户可以输入其右击地址细节的结算页面。另外,还将开创一个带有成立、读取、更新和删除效用的管理区,以便对成品分类开展管制并对该区域展开保养,以使只有记名的总指挥才能拓展改动。

面前早已介绍了自定义模型绑定器,现在到了添加五个新特点来成功购物车功用的时候了。首个特征将允许客户删除购物车物品,第二个特点将在页面的顶部展现购物车的摘要。

电子商务 2

后天内需在 CartController 类中定义那个 Checkout 方法:

面前早已确立了汪洋的基础结构,而且应用程序也开始确实地会聚在协同了,但从不把注意力放到其外观上。即使这本书不是一本有关
Web 设计或 CSS 的书,但SportsStore
应用程序设计也会因为太不佳的格式而破坏它的技能强度。本节将一部分正常的工作。

1.利用模型绑定

从上图可以见见,数据库中的所有成品都显得在一个纯净的页面上。本小结将添加对分页的帮忙,以便在一个页面上出示一定数额的制品,用户可以逐页查看所有产品分类。要贯彻这或多或少,可以在
Product 控制器中的 List 方法上添加一个参数,如下所示:

现行,当客户提供非法送货数据或试图对空购物车举办结算时,系统会向他们来得一些实惠的谬误信息,如下图所示:

 

电子商务 3

@model SportsStore.Domain.Entities.Product

<div class="item">
    <h3>@Model.Name</h3>
    @Model.Description
    <h4>@Model.Price.ToString("c")</h4>
</div>

近日,有了 IOrderProcessor 接口的一个兑现以及配置它的伎俩,便足以用
Ninject 来创制它的实例。编辑 SportsStore.WebUI 项目中的
NinjectController 类(在 Infrastructure 文件夹中),对 AddBindings
方法开展修改:

using SportsStore.Domain.Entities;
using System.Linq;

namespace SportsStore.Domain.Abstract
{
    public interface IProductRepository
    {
        IQueryable<Product> Products { get; }
    }
}

3.4 注册(接口)实现

...
body { font-family: Cambria, Georgia, "Times New Roman"; margin: 0; }
div#header div.title, div.item h3, div.item h4, div.pager a { font: bold 1em "Arial Narrow", "Franklin Gothic Medium", Arial; }
div#header { background-color: #444; border-bottom: 2px solid #111; color: white; }
div#header div.title { font-size: 2em; padding: .6em; }
div#content { border-left: 2px solid gray; margin-left: 9em; padding: 1em; }
div#categories { float: left; width: 8em; padding: .3em; }
div.item { border-top: 1px dotted gray; padding-top: .7em; margin-bottom: .7em; }
div.item:first-child { border-top: none; padding-top: 0; }
div.item h3 { font-size: 1.3em; margin: 0 0 .25em 0; }
div.item h4 { font-size: 1.1em; margin: .4em 0 0 0; }
div.pager { text-align: right; border-top: 2px solid silver; padding: .5em 0 0 0; margin-top: 1em; }
div.pager a { font-size: 1.1em; color: #666; text-decoration: none; padding: 0 .4em 0 .4em; }
div.pager a:hover { background-color: silver; }
div.pager a.selected { background-color: #353535; color: white; }
...

3.3 实现订单处理器

电子商务 4

 在这些应用程序中还索要一个零部件,以便可以对订单的细节举办处理。为了与
MVC
模型原理保持一致,本例打算为此效能定义一个托词、编写该接口的一个贯彻,然后用
DI 容器 Ninject 把双方关系起来。

 电子商务 5

 

@model SportsStore.WebUI.Models.ProductsListViewModel

@{
    ViewBag.Title = "Products";
}

@foreach (var p in Model.Products)
{
    Html.RenderPartial("ProductSummary",p);
}

<div class="pager">
    @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))
</div>

注:能够应用强类型的 Html.HiddenFor 帮忙器方法,为 ReturnUrl
模型属性创设一个隐藏字段,但这亟需运用基于字符串的 Html.Hidden
辅助器方法,对 ProductID 字段做同样的事情。
假设写成“Html.HiddenFor(x=>line.Product.ProductID)”,该帮助器方法便会渲染一个以“line.Product.ProductID”为名称的隐藏字段。该字段名与
CartController.RemoveFromCart
动作方法的参数名不般配,这会使默认的模子绑定器不能工作,由此 MVC
框架便无法调用此措施了。

正文的最后一个技巧是重构应用程序,以简化 List.cshtml
视图。本节打算创立一个分部视图(Partial
View)
,这种分部视图是放到在另一个视图中的一个内容片断。分部视图是自包含文件,且能够跨视图重用,这促进缩短重复,尤其是索要在应用程序的多少个地点渲染同样的数额时。

3.2 添加结算过程

2.2 成立模仿存储库

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

3.2 添加视图

IOrderProcessor
的兑现打算动用的订单处理形式是向网站管理员发送订单右击。当然,那简化了销售经过。大多数电子商务网站不会简单的殡葬订单邮件,而且也并未提供信用卡处理或任何支出模式的帮助,只是梦想把事情维持在关怀
MVC 方面,由此利用了这种发送邮件作为订单处理的点子。

4.4 创造实体框架上下文

可以见见,这里所增长的 Checkout 动作方法是用 HttpPost
注脚属性来修饰的,这标志该措施将用于对 POST 请求的处理 ——
在此例中,这是用户递交表单的时候。再一次重申,ShippingDetails
参数(这是行使 HTTP 表单数据自动创立的)和 Cart
参数(这是用自定义绑定器创设的)都要凭借于模型绑定器系统。

从这之后,所有基础工作均已就绪。此刻曾经有一个富含一个动作方法的控制器,该动作方法在默认
URL
被呼吁时被调用,它凭借于储存库接口的一个模拟实现,该存储库接口生成了一些粗略的测试数据。这一个测试数据被传送给与动作方法关联在同步的视图,而视图对每个产品创制一个简练的细节列表。运行该应用程序,效果图如下:

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

 

 

 像这样调用 View
方法(未指定视图名称),是告诉框架为该动作方法渲染一个默认视图。通过将
Product 对象的列表传递给这么些 View
方法,这是在给框架提供数据,以便用这多少个多少填充强类型视图中的 Model
对象。

由此落实 IModelBinder 接口,可以创造一个自定义模型绑定器。在
SportsStore.WebUI 项目中新建文件夹“Binders”,并新建类文件
CartModelBinder.cs ,代码如下:

4.1 成立数据库

3.6 显示验证错误

此书打算建立的那么些应用程序不只是一个浮泛的示范,而是要创造一个巩固且实际的、符合当下最实用要求的应用程序。由于要树立必要的底部结构,一起头的进度会有点慢。的确,若使用
WebForm,则足以更快地建立初期的出力,只要拖放一些与数据库直接绑定的长空即可。但在
MVC
应用程序中所付出的这一个先前时期工作,会带动可保障、可扩展以及社团能够的代码,且这个代码对单元测试具有卓越协理。一旦恰当地建好了这种基本的底层架构,前面的事体就会快起来了。

 

这是绝无仅有需要对产品分页的 URL 方案展开改动的地点。MVC
框架与路由功用是精心集成的。由此这样的改动将自行反映在 Url.Action
方法的处理结果中。如若运行这多少个应用程序,并导航到一个页面,将会师到那个新的
URL 方案在其效能。

今昔,客户可以开展从选拔产品到结算离开的全套购物过程。要是客户提供可行的的送货细节(且购物车中有物品),当他们点击“Complete
order”按钮时,便会看出一个谢谢页面,如下图所示:

注意:记得更新 SportsStore.WebUI 项目中
Entity Framework的本子,与 SportsStore.Domain
项目中的版本保持一致。不然报错。

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

本文打算实现一个暗含头部的经典式两列布局。

电子商务 6

5.添加分页

2.2 添加购物车摘要

6.3 创立分部视图

在上述代码中, MVC 框架会检查证实约束,这些约束是用多少阐明属性而选取于
ShippingDetails 的,并由此 ModelState
属性把地下状况传递给该动作方法。因而,可以通过检查 Model.IsValid
属性来查看是否留存问题。注意,倘使购物车中无物品,还调用
ModelState.AddModelError 方法注册了一条错误消息。

该属性指定了表名,并把 DbSet
结果的档次参数指定为实体框架用来代表表行的模型。在那一个例子中,该属性名是
Products(数据库中的表名)。即,希望用 Product 模型类型来代表 Products
表的次第行。

创办自定义模型绑定器

这就是储存库类,它实现了 IProductRepository 接口,并采纳了一个
EFDbContext 实例,以便用 Entity Framework
接收数据库的数目。在对该存储库添加特性时,便碰面到此间是何等使用 Entity
Framework 的。

模型绑定器可以通过请求中可用的音讯来创设 C# 类型,这是 MVC
框架的中央特性之一。本节将成立一个自定义模型绑定器来盖上 CartController
类。

 

...
<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>
...

运转该应用程序,可以观望曾经添加了页面链接,如下图所示。这一个链接的体制仍旧是很基本。首要的是这个链接能把客户从一个页面带到另一个页面,并浏览正在销售的成品。

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

4.2 定义数据库方案

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

电子商务 7

电子商务 8

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

namespace SportsStore.WebUI.Controllers
{
    public class ProductController : Controller
    {
        private IProductRepository repository;
        //指明用户希望每页显示4个产品
        public int PageSize = 4;

        public ProductController(IProductRepository productRepository)
        {
            this.repository = productRepository;
        }

        public ViewResult List(int page=1)
        {
            //从存储库获取 Product 对象,
            //按主键顺序排序,略过起始页之前出现的产品数,
            //然后取出由 PaeSize 字段指定的产品个数
            return View(repository.Products.OrderBy(p=>p.ProductID).Skip((page-1)*PageSize).Take(PageSize));
        }
    }
}

为了成功结算过程,需要向客户出示一个一度做到订单处理的确认页面,并感谢他们的购物。新建视图文件
Views/Cart/Completed.cshtml ,代码如下:

电子商务 9

电子商务 10

现行要做的全方位行事是报告 MVC 框架,抵达网站根的呼吁应该被映射到
ProductController 类的List 动作方法上。这足以因此编制 Global.asax.cs 的
RegisterRoutes 方法实现,代码如下:

 

namespace SportsStore.Domain.Entities
{
    public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }
}

今天到了体现 SportsStore
最终一个客户特性的时候了:结算并成功订单的能力。上边将扩充域模型,以提供收集用户送货细节的支撑,并加上一个甩卖这一个细节的特色。

 

注:构造器中的修改迫使用户需要对 CartController
类成立的单元测试举行立异。为新的构造器参数传递 null
,便会使单元测试可以透过编译。

终极一步是吧 Ninject 对模拟存储库的绑定替换为对实在存储库的绑定。编辑
SportsStore.WebUI 项目中的 NinjectControllerFactory 类,使 AddBindings
方法如下所示:

 

(3)添加视图模型视图

电子商务 11

2.1 创制一个浮泛的存储库

运作应用程序,可以看看效果:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace SportsStore.WebUI
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: null,
                url: "Page{page}",
                defaults: new { controller = "Product", action = "List" }
                );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
            );

        }
    }
}

 MVC 框架使用了一个叫作“模型绑定”的系列,以便通过 HTTP 请求来创建一些
C# 对象,目标是把它们当做参数值传递给动作方法。例如,MVC
处理表单的不二法门就是这么。框架会观望对象动作方法的参数,并用一个模子绑定器来得到表单中
input 元素的值,并把它们转换成同名的参数类型。

1.3 设置DI容器

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

电子商务 12

地点代码删除了 GetCart 方法,并对各类动作方法添加了 Cart 参数。当 MVC
框架接收到一个呼吁,比如,要求调用 AddToCart
方法时,会率先观察动作方法的参数,然后考察可用的绑定器列表,并打算找到一个可以创立每个参数类型实例的绑定器。这会要求自定义绑定器成立一个
Cart
对象,而此时通过选取对话状态特性来成功的。通过自定义绑定器的默认绑定器,MVC
框架可以成立一组调用动作方法所需要的参数,这让开发者可以重构控制器,以便在接收到请求时领悟什么样创设Cart 对象。

http://localhost:64245/?page=2

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

 

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

电子商务 13

运行程序即可以见到效用,对购物车添加物品时,物品数以及总费用都会追加,如下图所示:

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

为了使工作更简明些,也定义了 EmailSettings 类。 EmailOrderProcessor
的构造器需要以此类(EmailSettings 类)的一个实例,该实例包含了布置 .NET
邮件类所急需的全方位装置音讯。

一个更好的法子是特地创建一种听从可构成 URL 模式的方案。“可构成 URL
”是一种对用户有含义的法门,其情势如下:

3.5 完成购物车控制器

电子商务 14

 

电子商务 15

 

在 SportsStore.WebUI
项目中成立一个称谓”Infreastructure”的文书夹,然后创制一个名为”NinjectContrillerFactory
(Ninject 控制器工厂)”的类。

 

6.装置情节样式

 

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

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

        public ProductController(IProductRepository productRepository)
        {
            this.repository = productRepository;
        }

        public ViewResult List()
        {
            return View(repository.Products);
        }
    }
}

为明白决这一题目,我们打算成立一个自定义模型绑定器,以博得包含在对话数据中的
Cart
对象(注意,常规的模型绑定器可以直接处理请求中的数据来创立模型对象,这里开创自定义绑定器的目标是为着处理会话中的数据,手工用会话数据创立Cart 对象)。然后,MVC 框架可以创设 Cart 对象,并把它们作为参数传递给
Controller 类的动作方法。

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization"/>
        <add namespace="System.Web.Routing" />
        <add namespace="SportsStore.WebUI.HtmlHelpers"/>
      </namespaces>
    </pages>
  </system.web.webPages.razor>

2.完了购物车

3.3 设置默认路由

 

5.1 展现页面链接

这是一个简易的视图,它显得了购物车的物品数、那些物品的总费用,以及把购物车内容体现给用户的一个链接。现在,已经定义了有
Summary 动作方法所重回的这么些视图,能够在 _Layout.cshtml
文件中蕴藏它的渲染结果,代码如下:

Entity Framework
的摩登版包含了一个名为“Code-first(代码先行)”的很好的特征。其思想是足以先定义模型中的类,然后再通过这几个类生成数据库。

对于本例的目的而言,所关切的是 ControllerContext 类,它兼具 HttpContext
属性,它又呼应地有一个 Session
属性,该属性可以拿到和安装会话数据。通过读取会话数据的键值可以获得Cart,而在对话中还不曾 Cart 时,又有何不可创设一个 Cart 。

今日亟待为 List 动作方法添加默认视图。添加对应的视图文件 List.cshtml
,并渲染视图文件如下:

 

 

运转程序,可以看看该视图是怎么渲染的,效果如下图所示,该视图为采访客户的送货细节渲染了一个表单。

亟待报告 Entity Framework 咋样连接到数据库,为了成功这一工作,只需要在
SportsStore.WebUI 项目标 Web.config
文件中上述下文类同样的名字添加一条数据库连接字符串即可,如下所示

正文将继续构建 SportsStore
示例应用程序。在上一章中,添加了对购物车的基本匡助,现在打算改进并成功其效用。

上述清单遵守了在协同独立的 Visual Studio
项目中定义域模型的预约,即类必须标记为
public,尽管不必然要遵守这一预定,但那样做促进保持模型与控制器分离。

行使这一个附件,现在得以让客户理解自己的购物车中有怎么着。这一个附件也出示的提供了一个结算离店的法门。从中再几遍探望用
RenderAction 把一个动作方法所渲染的出口组合到一个 Web
页面是何其容易。这是将应用程序功效分解成清晰可接纳模块的一种很好的技术。

http://localhost/Page2

 

Ninject 会一贯以该模仿对象来满意对 IProductRepository
接口的哀告,而不是历次都创设一个新的贯彻目标实例。

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

 

能够看出,这是一个概括的措施。它只需要渲染一个视图,以最近 Cart
(它是自定义模型绑定器拿到的)作为视图数据。还需要一个分部视图,它在对这一个Summary 方法调用做出响应时被渲染。添加对应的 Summary
视图文件,代码如下:

using SportsStore.Domain.Entities;
using System.Collections.Generic;

namespace SportsStore.WebUI.Models
{
    public class ProductsListViewModel
    {
        public IEnumerable<Product> Products { get; set; }
        public PagingInfo PagingInfo { get; set; }
    }
}

这一个 IModelBinder 接口定义了一个格局: BindModel
。所提供的两个参数使得创设域模型对象变成可能。 ControllerContext
对控制器类所兼有的上上下下消息提供了拜访,这个音讯包含了客户端请求的细节。
ModelBindingContext
提供了要求确立的模子对象的音讯,以及使绑定更易于处理的工具。

这是ASP.NET MVC 框架典型的支出形式。

 

新的绑定以粗体呈现,它告诉自己 Ninject ,用户愿意创造 EFProductRepository
类的实例来对 IProductRepository
接口的请求举办劳动。再度运行应用程序,效果如下:

电子商务 16

在 SportsStore.Domain 项目中创建一个名为 Abstract
的顶层新文件夹,并创建一个名为 IProductRepository 的新接口,代码如下:

最近得以改进 CartController 类,删除 GetCart
方法而借助现在的模子绑定器,MVC 框架会自动地应用它,代码如下:

6.1 定义布局中的公用内容

 

Visual Studio
解决方案是一个含有一个或七个品种的容器。示例应用程序需要六个档次,如下图所示:

提示:倘使没有可用的 SMTP 服务器也没涉及,可以将
EmailSettings.WriteAsFile属性设置为true,这样会把邮件音讯作为文件写到由
FileLocation
属性指定的目录。该目录必须依照存在且是可写入的。邮件文件的壮大名将为
.eml ,但它们可以被其他文件编辑器所读取。

为了援助 HTML
援助器方法,本文打算把可用页面数、当前页、已经储存库中产品总数等地点的音信传送给视图。做这种事最容易的艺术是创设一个视图模型,在
SportsStore.WebUI 文件夹 Models 中新建类文件 PagingInfo.cs ,代码如下:

首先,需要对 CartController 类添加一个概括的办法,代码如下:

运作应用程序,效果不变。

本例用 Html.EditorFor 协助器方法为每个表单字段渲染了一个 input
元素。该方法是模板化援助器方法的一个事例(注意,对模型对象的每个属性使用的都是
Html.EditorFor
扶助器方法,并未针对各种属性的档次,去选定特定的协助器方法)。它让 MVC
框架去决定一个视图模型属性需要利用哪个种类 input
元素,而不是举办了解的指定(例如,使用 Html.TextBoxFor)。

在“数据库资源管理器”窗口中,展开 SportsStore 数据库的“表”条目,右击
Products 表,采纳“显示表数据”,然后输入下图所示数据。可以用 Tab
键逐行移动光标。在一行的尾声按 Tab
键,将移到下一行并改进数据库中的数据。

 

这些 PageLinks 扩展方法运用 PagingInfo
对象中提供的音信生成一组页面链接的 HTML 。Func
参数提供了在其间传递委托的力量,该信托用于转移查看其他页面的链接。

 

页面链接尽管可以其效能,但它们拔取的依然是询问字符串,以便将分页音信服务器,代码如下:

像这样使用自定义模型绑定器有多少个便宜。第一个便宜是把用来成立 Cart
与创造控制器的逻辑分离开来了,这让开发者可以修改存储 Cart
对象,而不需要修改控制器。第二个便宜是其他利用 Cart
对象的决定器类,都可以简单地把这个目标阐明为动作方法参数,并可以使用自定义模型绑定器。第六个便宜是它亦可对
Cart 控制器举办单元测试,而不需要效法大量的ASP.NET 通道。

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;

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
            Mock<IProductRepository> mock = new Mock<IProductRepository>();
            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<IProductRepository>().ToConstant(mock.Object);
        }
    }
}
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);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SportsStore.WebUI.Models
{
    public class PagingInfo
    {
        public int TotalItems { get; set; }
        public int ItemsPerPage { get; set; }
        public int CurrentPage { get; set; }

        public int TotalPages
        {
            get { return (int)Math.Ceiling((decimal)TotalItems / ItemsPerPage); }
        }
    }
}

内需告诉 MVC 框架,它可以选用 CartModelBinder 类来创建 Cart
的实例。这需要在 Global.asax 的 Application_Start 方法中展开登记:

那一个修改将一个 ProductsListViewModel 对象作为模型数据传递给了视图。

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

本小结将创设一个控制器和一个动作方法,它可以显得存储库中的产品细节。此刻,将只只针对模仿存储库中的数据。

<!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>

新建控制器”ProductController”,模板为”空 MVC 控制器”,修改代码如下

电子商务 17

本文打算对该数据库手工添加一些数据。

3.递交订单

电子商务 18

3.1 增添域模型

1.4 运行程序

后面早已定义了控制器中的 RemoveFromCart
动作方法,因而,让客户删除物品只可是是在视图将官以此点子表透露来的业务。本文打算在购物车摘要的每一行中添加一个“Remove”按钮来做这件事。对
Views/Cart/Index.cshtml 所做的修改如下:

 

只要客户输入了非法的错误音讯,有问题的这一个非法表单字段将被高亮,但没有音信被出示出来。更糟的是,假诺客户准备对一个空购物车举行结算,这不会成功那多少个订单,但客户却一贯看不到任何错误音信。为精晓决那么些题目,需要对视图添加一个讲明摘要。下边代码呈现了增长到
Checkout.cshtml 视图的内容。

据悉提醒,登录数据库,并新建数据库 SportsStore

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

6.2 添加CSS样式

    <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>
@model SportsStore.WebUI.Models.ProductsListViewModel

@{
    ViewBag.Title = "Products";
}

@foreach (var p in Model.Products)
{ 
    <div class="item">
        <h3>@p.Name</h3>
        @p.Description
        <h4>@p.Price.ToString("c")</h4>
    </div>
}

<div class="pager">
    @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }));
</div>
@{
    ViewBag.Title = "SportsStore: OrderSubmitted";
}

<h2>Thanks!</h2>
Thanks for placing your order. We'll ship your goods as soon as possible.
using SportsStore.WebUI.Models;
using System;
using System.Text;
using System.Web.Mvc;

namespace SportsStore.WebUI.HtmlHelpers
{
    public static class PagingHelpers
    {
        public static MvcHtmlString PageLinks(this HtmlHelper html, PagingInfo pagingInfo, Func<int, string> pageUrl)
        {
            StringBuilder result = new StringBuilder();
            for (int i = 1; i <= pagingInfo.TotalPages; i++)
            {
                TagBuilder tag = new TagBuilder("a"); // 构造一个<a>标签
                tag.MergeAttribute("href", pageUrl(i));
                tag.InnerHtml = i.ToString();
                if (i == pagingInfo.CurrentPage)
                    tag.AddCssClass("selected");
                result.Append(tag.ToString());
            }
            return MvcHtmlString.Create(result.ToString());
        }
    }
}
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());
        }
    }
}
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System.Linq;

namespace SportsStore.Domain.Concrete
{
    public class EFProductRepository:IProductRepository
    {
        private EFDbContext context = new EFDbContext();
        public IQueryable<Product> Products {
            get { return context.Products; }
        }
    }
}
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
            });
        }
    }
}

电子商务 19

概念接口

电子商务 20

为了缓解这一问题,本节打算添加一个小部件,它汇聚购物车的内容,并可以透过点击来展现购物车内容。下边将动用与充裕领航不见卓殊相似的章程来完成这一行事——作为一个动作,把它的出口注入到
Razor 布局。

该接口使用了 IQueryable<T> 接口,以便可以取得一密密麻麻 Product
对象,而不必表达数据如何存储、存储在啥地方,以及咋样接收数据。使用这一
IProductRepository 接口的类,可以收获 Product
对象而无需知道它们出自何方或什么递交它们,这是存储库形式的面目。在添加特性的漫天开发过程中,将重新审视这一接口。

现在早就落实了一个效率化的购物车,但将该购物车集成到解密的措施还设有一个问题:只有因而查阅购物车摘要屏幕,客户才能清楚她们的购物车里有些什么。而且,他们只得通过把一个新的物料进入购物车,才能来看购物车的摘要屏幕。

 

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

前方建立的都是粗略的MVC程序,现在到了吧所有事情综合在一块儿,以建立一个概括但实在的电子商务应用程序的时候了。

2.1 删除购物车物品

 

3.7 呈现致谢界面

近年来,可以革新 ProductController 类中的 List 方法,以便利用这些ProductsListViewModel
类,给视图提供在页面上突显的出品细节和分页细节,修改后代码如下:

落实接口

(2)添加 HTML 协理器方法

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

5.2 改进 URL

人们爱好使用 Cart 控制器中的会话状态特性来储存和保管 Cart
对象,但却不爱好它要利用的做事章程。它不适合本应用程序模型的另外部分,而那是按照动作方法参数的(因为动作方法参数的操作以模型为根基,而对话状态的操作需要设置键值对,两者的工作章程不均等)。另外。除非模仿基类的
Session 参数,否则不可能适当的对 CartController
类举行单元测试,而这意味需要效法 Controller
类(控制器的基类),以及其它部分不指望处理的东西。

 

using SportsStore.Domain.Entities;

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

本条例子修改了 @model 指示符,以告知
Razor,现在正值采用一个不同的数据类型。也需要更新 foreach
循环,以使数据源是模型数据的 Products 属性。

 

 

 

只有隐含扩充方法的命名空间在界定内时,其中的扩大方法才是可用的。在一个代码文件中,这是用
using 语句来成功的;但对于一个 Razor 视图,必须把一个安排条目添加到
Web.config 文件中,或在这么些视图上添加一条 @using 语句
。容易模糊的是,在一个 Razor 的 MVC 项目中有六个 Web.config
文件:主配置文件位于应用程序的根目录,而视图专用的配置文件位于 Views
文件夹。需要修改的是 Views/Web.config 文件,如下所示:

上述代码创制了一个 EmailSettings 对象,将其用于 Ninject 的
WithConstructorArgument 方法,以便在急需成立一个新实例对 IOrderProcessor
接口的伸手举办劳动时,把它注入到 EmailOrderProcessor
构造器中。上述代码只为 EmailSettings 中一个特性 WriteAsFiles
指定了值。使用 ConfigurationManager.AppSettings
属性来读取该属性的值,这让用户可以访问已经身处 Web.config
文件中的应用程序,在应用程序配置文件 Web.config 中读取这些 WriteAsFile
属性的装置值,这也是从应用程序配置文件 Web.config
中读取某个属性设置值的方法。配置文件修改如下:

 

为了接纳 Code-First 特性,需要成立一个派生于
System.Data.Entity.DbContext
的类。这一个类会为用户要拔取的数据库中的每个表自动地定义一个特性。

首先步,是将Entity Framework (此处是6.1本子)添加到 SportsStore.Domain
项目中。通过 管理NuGet 包,安装新型的 Entity Framework 包。

万一展开到这一步,表达 Visual Studio 2012 和ASP.NET MVC
开发环境的准备干活展开的相当如愿。

3.4 运行应用程序

而为了便利客户。需要在各类产品列表的底层渲染一些页面的链接,以使客户可以在不同的页面之间导航。为了达到这一目标,本文打算实现一个可接纳的
HTML 扶助器方法,它相仿于事先 【MVC 4】1.第一个 MVC
应用程序
中使用的
Html.TextBoxFor 和 Html.BeginForm
方法。该帮忙器方法将为所急需的领航链接生成 HTNL 标记。

当前还没办好利用 HTML 协理器方法的预备,还索要把这多少个 PagingInfo
视图模型类的一个实例提供给视图。可以用 View Bag
(视图包)特性来做这件事,不过一个更好的艺术是把控制器发送给视图所有数据封装成一个纯净的视图模型类。为此,需要把一个新的名为“ProductsListViewModel”
的类添加到 SportsStore.WebUI 的 Models 文件夹。

在一个 Razor
视图中需要引用的每一个命名空间,都亟待以那种方法开展宣示,或在视图中用
@using 语句进行宣示。

4.3 向数据库添加数据

电子商务 21

这里必须对该问卷添加一些命名空间,但用来创建模仿存储库实现的经过使用的是 【MVC
4】4.MVC 基本工具(Visual Studio
的单元测试、使用Moq)

所介绍的一样的 Moq 技术。AsQueryable方法是一个 LINQ 扩充方法,它将
IEnumerable<T> 转换成
IQueryable<T>,此处需要它来配合接口签名。

即便尚无增长此外绑定,但在急需时,可以接纳AddBindings 方法。必须告诉 MVC
希望利用此 NinjectControllerFactory 类来创建控制器对象,其艺术是在
SportsStore.WebUI 项目中 Global.asax.cs 文件的Application_Start
方法中添加一些代码,如下粗体部分所示:

 

点击左上角的”更新”按钮,会看出该语句的功力摘要。

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;

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>();
        }
    }
}
<!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">
        <div class="title">SPORTS STORE</div>
    </div>
    <div id="categories">
        We will put something useful here later
    </div>
    <div id="content">
        @RenderBody()
    </div>
</body>
</html>

 

using SportsStore.Domain.Entities;
using System.Data.Entity;

namespace SportsStore.Domain.Concrete
{
    public class EFDbContext:DbContext
    {
        public DbSet<Product> Products { get; set; }
    }
}

运转程序,会看出一个荒谬页面,这是因为所请求的 URL 是与 Ninject
尚未开展绑定的控制器相关联的:

(1) 添加视图模型

为了抬高分部视图,右击 SportsStore.WebUI 项目中的 /Views/Shared
文件夹,添加新的视图文件 ProductSummary.cshtml 。

继而更新 Views/Product/List.cshtml ,以使它亦可采取这么些分部视图。

3.来得产品列表

填补到 Visual Studio 2012 和 SQL Server 2012 中的一个很好的特色是
LocalDB。它是专门为开发者而计划的一个免管理的SQL Server
主旨功用实现。使用该特性使大家在确立项目、以及背后将数据库部署到总体版的
SQL Server 期间,可以跳过数据库的装置过程。

 

  <connectionStrings>
    <add name="EFDbContext" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=SportsStore;Integrated Security=True" 
providerName="System.Data.SqlClient" />
  </connectionStrings>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace SportsStore.WebUI
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
            );
        }
    }
}

1.1 成立 Visual Studio 解决方案和项目

修改视图文件如下:

 

 

 

现今,已经定义了一个架空接口,可以实现持久化机制,并将其挂接到一个数据库。本文打算在后头部分再做这件工作。为了可以起先编制应用程序的另外一些,本文打算创造一个
IProductRepository 接口的模仿实现。本文打算在 SportsStore.WebUI 项目的NinjectControllerFactory 类的 AddBindings 方法中做这件事,代码如下:

 

 

2.从域模型起头

电子商务 22

 

1.开始

4.5 创建 Product 存储库

using SportsStore.Domain.Abstract;
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 ProductController : Controller
    {
        private IProductRepository repository;
        public int PageSize = 4;

        public ProductController(IProductRepository productRepository)
        {
            this.repository = productRepository;
        }

        public ViewResult List(int page = 1)
        {
            ProductsListViewModel model = new ProductsListViewModel
            {
                Products = repository.Products.OrderBy(p => p.ProductID).Skip((page - 1) * PageSize).Take(PageSize),
                PagingInfo = new PagingInfo
                {
                    CurrentPage = page,
                    ItemsPerPage = PageSize,
                    TotalItems = repository.Products.Count()
                }
            };
            return View(model);
        }
    }
}

 

电子商务 23

那很适合绿地(格林(Green)-field)开发项目,但这个连串并不多见。因而,本文打算演示下
Code-First 的一种变异,以此把模型类与现有的数据库关联在联名。

前面的篇章 【MVC 4】3.MVC 基本工具(制造示范项目、使用
Ninject)

体现过哪些利用 Ninject 创建一个自定义倚重性解析器,以便 MVC
框架用它创立整个应用程序实例化对象。这里打算选拔不同的法门,即创制一个自定义的控制器工厂。用户可以在里头添加自定义代码,以改变MVC框架的(默认)行为,或者像这里所做的均等,将DI
限制到应用程序的一有的。常用的情势是用依赖性解析器来拍卖
DI,而用自定义控制器工厂来改变查找控制器类的法子,但在此例中,只打算动用控制器工厂。

修改引用的 CSS 样式文件 Site.css 如下:

3.1 添加控制器

眼前已经得以来得含有产品细节的简练视图,但其出示的知识模仿的
IProductRepository
所重返的测试数据。在可以显得真实数据的存储库此前,还亟需建立一个数据库,并用部分数额填充它。

 

...
ninjectKernel.Bind<IProductRepository>().ToConstant(mock.Object);
...

 

人们愿意,Ninject 无论什么日期收到到一个 IProductRepository
接口实现的请求,都回到同样的依样画葫芦目的,这便是采取 ToConstant 方法的缘故

方今亟需某种格局来拿到数据库中的Product
实体。正如前方博文所诠释的,人们愿意持久化逻辑与域模型实体是分另外——此事经过应用存储库格局来贯彻。此刻,不必顾虑会如何落实持久化,不过,将从概念它的接口来初叶这一进程。

 

其一事例已经去掉了事先的 List.cshtml 视图中的 foreach
循环中的标记,并把它改成了那个新的分部视图中。用 Html.RenderPartial
帮助器方法来调用这么些分部视图,参数是视图的称号和视图模型对象。

MVC
应用程序中有太多的工作都是围绕域模型而开展的,由此,域模型是起首工作的极品地方。

 

这将会来得创建新表的设计器。使用 T-SQL 窗口,输入相应的SQL语句创制数量表
Products 。

正文打算创立一个富含两个门类的Visual Studio
解决方案,一个项目包含域模型,一个是MVC
应用程序,第七个则带有单元测试。首先用”空白解决方案”模板创设一个名为”SportsStore”的新的Visual
Studio 解决方案。

电子商务 24

参照前边的博文 【MVC 4】3.MVC 基本工具(创造示范项目、使用
Ninject)
 
和 【MVC 4】4.MVC 基本工具(Visual Studio
的单元测试、使用Moq)
 
对库和品种抓好科学的引用。所需的品类依赖性如下图所示:

这会儿,视图期望的是一个 Product 对象的行列,由此需要革新 List.cshtml
,以拍卖这么些新视图模型类型,修改后代码如下:

1.2 添加引用

(4)展现页面链接

碰巧的是,MVC 很容易修改 URL 方案,因为它应用了 ASP.NET
的路由特性。所要做的学问吧一条新路由添加到 Global.asax.cs 中的
RegisterRoutes 方法,如下所示:

近年来有了那些视图模型,便可以实现这么些 HTML
匡助器方法了,该办法称为“PageLinks”。在 SportsStore.WebUI
项目中开创一个新文件夹“HtmlHelpers”,并充裕一个新的静态类“PagingHelpers(分页匡助器)”。类公事的情节如下所示:

今天,本文已经做好了确实贯彻 IProductRepository 类所需要的各类准备。 在
SportsStore.Domain 项目标 Concrete
文件夹中添加一个类,取名”EFProductRepository”,代码如下:

下一个手续是成立一个将前方建立的简单模型与数据库关联起来的上下文类(Context
Class)。

@model IEnumerable<SportsStore.Domain.Entities.Product>

@{
    ViewBag.Title = "Products";
}

@foreach (var p in Model) { 
<div class="item">
    <h3>@p.Name</h3>
    @p.Description
    <h4>@p.Price.ToString("c")</h4>
</div>
}

 

假设运行这些应用程序,将见到只有多少个条文展现在页面上。假设想查看另一页,能够把询问字符串参数加到
URL 的结尾,如下所示:

新建的数据库只需要一个数据表,用以存储 Product
数据。右击数据库对应的”Tables(表)”条目,新增数据表。

 

提示:RenderPartial 方法并不像大多数别样协理器方法那样重返 HTML
标记。相反,它把内容从来写入到响应流,因此必须用一个分行,像一个完全的
C# 程序行一样来调用它。这比缓存已渲染的分部视图的 HTML
更实用一些,因为它将被写到响应流。假诺喜欢用一种更平等的语法,可以动用
Html.partial 方法,它形成与 RenderPartial 方法同样的功力,但回到的是一个
HTML 片段,并且可以像 @Html.Partial(“ProductSummary”,p) 一样采取它。

电子商务 25

视图模型并不是域模型的一片段,它只是一种便民在控制器与视图之间传递数据的类。为了强调这点,将这一个类位居
SportsStore.WebUI 项目中,以使它与域模型的类分离开(将视图模型放在 MVC
框架项目的 Models
文件夹,而不是坐落类库项目中,那种做法得以验证视图模型不是域模型,明确了概念,也使应用程序的社团更清晰)。

始建一个新文件夹
“Concrete”,并在中间添加一个名为”EFDbContext”的新类。代码如下:

重点的是吗这条路由加在 Default
路由从前。路由是按它们列出的依次举行拍卖的,这里需要这条新路由预先于已经存在的这条。

http://localhost/?page=2
using Ninject;
using System;
using System.Web.Mvc;
using System.Web.Routing;

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

        }
    }
}
@model SportsStore.WebUI.Models.ProductsListViewModel

@{
    ViewBag.Title = "Products";
}

@foreach (var p in Model.Products) { 
<div class="item">
    <h3>@p.Name</h3>
    @p.Description
    <h4>@p.Price.ToString("c")</h4>
</div>
}

 

一旦运行程序,会看出其外观已经拿到立异,效果图如下:

率先个步骤是在 Visual Studio
中开创数据库连接。从”View(视图)”菜单中开拓”Database
Explorer(数据库资源管理器)”窗口,点击”Connect to
Database(连接到数据库)“按钮。

本文打算以 SQL Server 作为数据库,并用 Entity Framework(实体框架 –
EF)来拜会数据库,EF 是 .NET 的ORM(对象关系映射)框架。ORM
框架让开发人士可以用规则的C# 对象来采纳关系数据库的表、列和行。 LINQ
可以与不同的数据源一起工作,其中之一就是 Entity Framework 。

Leave a Comment.