【Pro ASP.NET MVC 3 Framework】.学习笔记.7.SportsStore:购物车
3 创建购物车
每个商品旁边都要显示Add to cart按钮。点击按钮后,会显示客户已经选中的商品的摘要,包括总金额。在购物车里,用户可以点击继续购物按钮返回product目录。也可以点击Checkout now按钮,完成订单和购物会话。
3.1 定义Cart Entity
购物车是程序业务域的一部分,在我们的领域模型中创建实体。添加一个Cart类到Entities文件夹。
购物车类使用CartLine,代表用户选中的一个商品。定义了添加、移除、计算合计、清空的方法。我们也提供了一个属性,返回IEnumerble<CartLine。
3.1.1 测试购物车单元测试
Cart类相对简单,但有一些非常重要的行为,我们必须确保工作正常。一个功能不良的购物车会破坏程序的整体。偶们对这些特性一个一个测试。
第一个要测试的行为,是将添加货物到购物车。如果该商品是第一次被加到购物车,我们需要一个新的CartLine。
如果客户已经添加过该商品,我们需要增加相应CartLine的数量,而不是创建一个新的。
我们也需要检查移除商品的功能。
计算总金额的功能:
最后测试的是清空功能
3.2 Add to Cart按钮
改变ProductSummary.cshtml局部视图。当表单被提交时,会提交到Cart controller中的AddToCart action方法。
默认地,BeginForm helper方法创建一个表单,使用HTTP POST方法。可以变为GET方法。
3.2.1 在同一个地方创建多个HTML FORMS
使用HTML.BeginForm helper在每个商品列表,意味着每个Add to cart按钮会被渲染在相互分隔的自己的HTML from元素中。在ASP.NET Web Forms中,一个页面限制只有一个form。ASP.NET MVC不限制每个页面的form数量,你要多少可以加多少。
不同form返回到相同的controller方法,伴随着不同的参数值,这是一个很好而且简单的方法,来处理button点击。
3.3 实现Cart Controller
我们需要创建一个CartController,来处理Add to cart按钮的点击。
这里有几个点。第一个是ASP.NET session状态特性,存储并检索Cart对象,这是GetCart方法的目的。ASP.NET有很好的session特性,使用cookis或URL重写用户的关联请求,从form一个单一浏览session。相关的的特性是session状态,它允许我们用session关联数据。这是一个适合偶们Cart类的想法。偶们像让每个用户有自己的购物车,我们想让购物车固定在不同的请求。数据关联到session,session过期时会删除。这意味着我们不需要管理Cart类的存储或生命周期。
Session装填对象,默认存储在Asp.net服务器的内存中。你可以配置一个不同的存储路径,包括使用Sql数据库。
在AddToCart和RemoveFromCart方法中,我们使用参数名匹配HTML form中输入的元素。这允许MVC框架关联POST变量传递来的参数,意味着我们不需要手动处理。
3.4 显示Cart的Content
RedirectToAction方法,它的效果是,发送一个HTTP重定向指令,到客户端浏览器,让浏览器请求一个新的URL。在这个例子中,我们让浏览器请求Cart controller的Index action。
我们会实现Index方法,用它播放Cart的contents。偶们需要传递两个信息碎片给view:Cart对象和如果用户点击继续购物按钮后要显示的URL。为了这个目的,我们会创建一个简单的视图模型类,CartIndexViewModel。
然后在CartController中添加Index方法
并使用CartIndexViewModel(SportsStore.WebUI.Models)创建强类型视图。我们想在显示cart的content时,一如既往地与程序的其他部分页面一样,所以没有填layout,它会默认地使用_Layout.cshtml文件。
它枚举购物车中的行,将每行添加到HTML表,伴随着每行总额和购物车总额。当我们点击继续购物按钮,会回到来时的页面。
4 使用模型绑定
MVC框架使用一个叫做model binding的系统,从来自HTTP查询,创建C#对象,为了将他们作为参数值传递给action方法。这是MVC如何处理表单。框架查看被触发的action方法的参数,并使用一个model binder,得到表单input元素的值,并使用相同的名字,将他们转换为参数的类型。
Model binders可以从有效查询的任何信息创建C#类型。这是MVC框架的中心特性之一。我们要创建一个自定义的模型绑定,来改进CartController类。
我们喜欢使用session状态特性,来存储和管理Cart对象。但是我们确实不喜欢这种方式。它不适合我们其他部分的程序模型,那些基于action方法参数。我们不能在CartController类使用单元测试,除非我们Mock Session,这意味着mocking真个controller类。
为了解决这个问题,我们要创造一个自定义model binder,获得session data中包含的cart对象。MVC框架然后会创建Cart对象,传递他们作为参数给action方法。模型绑定特性是非常强大和灵活的。
4.1 创建自定义Model Binder
我们创建自定义model binder,以实现IModelBinder接口。在SportsStore.WebUI中新建Binders文件,在它里面创建CartModelBinder类。
IModelBinder接口定义了一个方法:BindModel。两个参数用来创建领域模型对象。ControllerContext提供访问controller拥有的所有信息,包括客户端的查询详情。ModelBindingContext给你关于你将要构建的模型对象的信息。
出于这个目的,ControllerContext类是我们感兴趣的。它由HttpContext属性,它可以给我们sesson属性,并设置session data。偶们通过读取session data的key的value,获得Cart,如果它不存在,就创建它。
偶们需要告诉MVC框架,它可以使用CartModelBinder类,创建Cart的实例。在Global.asax的Application_Start中添加
现在我们可以将GetCart从CartController中移除,并使用我们的模型绑定。
我们移除了GetCart方法,并为每个action方法添加了Cart参数。当MVC框架收到请求,AddToCart方法被调用,它开始查找action方法的参数。它查看可用绑定的列表,尝试着找到一个能创建参数类型的实例。我们自定义的绑定,被要求创建一个Cart对象,它使用session状态特性完成工作。在我们的绑定和默认绑定之间,MVC框架会创建一组调用action方法必须的参数。允许我们重构controller。
使用自定义绑定有一些益处。第一,偶们分离了用来创建Cart的逻辑,从Controller。它允许偶们改变我们存储Cart对象的方法,而不需要改变controller。第二,用到Cart对象的任何Controller类,都能简单地将他们声明为action的参数,并改进自定义模型绑定。第三,是最重要的一点,偶们可以对Cartcontroller进行单元测试了,而不需要mock许多ASP.NET管道。
4..2 使用单元测试cart controller
通过创建Cart对象,并将他们传递给action方法,我们可以测试CarController类。需要测试controller的三个不同的方面:
- AddToCart方法应该添加被选择的product到用户的cart
- 在添加product到cart后,需要重定向到Index View
- 用户返回到分类的url,必须准确地传递给Index action方法
5 完成购物车
添加两个心的特性,第一个是移除商品,第二个是在页面顶部显示商品总数
5.1 从购物车移除商品
我们已经定义并而是了RemoveFromCart action方法,需要把它放到视图,在购物车汇总的每一行添加Remove按钮。
我们可以使用强类型Html.HiddenFor helper方法,为模型属性ReturnUrl创建一个隐藏域,但是我们需要使用基于字符串的Html.Hidden helper为ProductID域。如果我们写成
helper会渲染一个
的 field。field的name不能匹配CartController.RemoveFromCart action放的的参数名,它会防止默认的模型绑定工作,所以MVC框架不能调用这个方法。
name与参数名相匹配。
5.2 添加购物车汇总
我们需要把购物车放在界面上。客户可以屏幕上看到购物车中,商品的数量。他们可以看到一个一个新商品进入购物车。
要做到这点,我们需要添加一个控件,汇总购车的contents,被点击后显示购物车的contents。这和导航控件很相似,做一个注入到Razor layout的action。
在CartController中添加
它仅需要渲染一个视图,提供当前Cart(从我们自定义的模型绑定中获得)作为视图数据。我们需要创建一个局部视图,它会在Summary方法被调用时,在response中被渲染。创建Summary的局部视图,强类型Cart。
在_Layout.cshtml文件中?:
使用RenderAction,结合action方法,渲染输出到页面。这是个不错的技术,打碎了程序的功能,使之成为不同的,可以再度重用的块。
6 提交订单
现在,偶们到达了最后一个客户特性,结账的能力和完成订单。接下来,我们会扩展领域模型,支持从用户捕捉购物明细,并添加处理这些细节的特性。
6.1 扩展领域模型
在Entities中添加ShippingDetails类,这个类代表了用户的购物明细。
使用了System.ComponentModel.DataAnnotations的验证属性。必须添加引用才能使用。ShippingDetails类中没有任何函数,所以我们没有明显的单元测试。
6.2 添加结账处理
我们的目标是用户可以输入他们的购物详情,并提交订单。我们需要添加Checkout now按钮到Views/Cart/Index.cshtml文件。
这个按钮调用了Cart/Checkout,所以要在CartController类中添加Checkout方法。这个方法返回默认视图,并传递一个新的ShippingDetails对象,作为视图模型。创建强类型视图,视图模型为ShippingDetails。
使用Html.EditorFor helper方法,为每个表单域渲染了input元素。这个方式是一个templated view helper。我们让MVC框架画出input元素类型的必须的视图模型属性,而不是明确地使用Html.TextBoxFor指定它。
我们看到模板视图助手,多么只能地为我们的bool属性,渲染了一个checkbox。为string属性渲染了textbox。
我们将来会使用Html.EditorForModel helper方法,它会为ShippingDetails视图模型类的所有属性生成一个label和一个inputs。然而,我们想将name,address区分开来,并且显示在表单的不同区域,所以简单地直接参照每个属性。
6.3 实现Order Processor
我们需要一个组件,提交订单给处理。为了保持MVC模型的原则,我们为这个功能定义一个接口,并写一个它的实现,关联到DI容器和Ninject。
6.3.1 定义接口
在Abstrack文件夹中创建新接口IOrderProcessor。
6.3.2 接口的实现
IOrderProcessor的实现,用来处理订单,发e-mail给管理员。当然,我们简化了销售过程。大多数电子贸易网站,不会简单地将order发e-mail,但是我们不提供处理信用卡或其他形式的支付的支持。我们只想关注MVC,所以经它发e-mail。
在Concrete文件夹中创建EmailOrderProcessor类。这个类使用了.NET框架内建的SMTP支持,来发送e-mail。
为了让事情变得简单,我们定义了EmailSettings类,EmailOrderProcessor的构造器方法需要这个类的实例,它包含.NET e-mail类需要的所有配置。
不要担心没有SMTP可用,如果设置了EmailSetting.WriteAsFile属性为true,e-mail messages会被直接写入FileLocation指定的文件。这个途径必须存在而且可以写入。文件会以.eml扩展。
6.4 注册实现
现在偶们有了IOrderProcessor接口的实现,意味着可以配置它。我们可以使用Ninject创建它的实例。在NinjectControllerFactory类中添加绑定。
我们创建了一个EmailSettings对象,当IOrderProcessor接口被请求创建一个新的实例时,偶们使用Ninject WithConstructorArgument方法将它注入到EmailOrderProcessor的构造器中。我只指定了一个属性,WriteAsFile。它允许我们访问Web.config文件中的程序设置。
6.5 完成Cart Controller
要完成CartController类,我们需要修改构造函数,让它需要一个IOrderProcessor接口的实例,并添加一个新的action方法,处理当用户点击Complete Order按钮时的HTTP表单POST。
Checkout方法使用HttpPost属性装饰,这意味着它会通过POST查询的方式调用。当用户提交表单。再一次,你依赖模型绑定系统,包括ShippingDetails参数(它通过HTTP表单数组自动被创建)和Cart参数(它使用自定义绑定创建)。
这个改变需要我们变更CartController类的单元测试,传递Null为新的构造器参数。
MVC框架会检查ShippingDetails的date annotation属性的验证约束。任何违反的都会通过ModelState属性传递给action方法。我们可以通过检查ModelState.IsValid属性,看看这里有没有问题。注意,如果购物车为空,我们调用Modelstate.AddModelError方法,注册一个错误消息。
6.5.1 订单处理的单元测试
要使得CartController类的单元测试变得完整,需要测试Checkout的重写方法。
测试确保了不能check out使用空购物车。我们检查这点,通过确保mock IOrderProcessor实现的ProcessOrder永远不会被调用。model state被标记为invalid,传递给view。
6.6 显示验证错误
如果用户输入不能通过验证的信息,这个表单域就会高亮,但不显示错误信息。如果用户使用空的购物车结账,我们不让他完成订单,但是它看不到任何错误信息。为了解决这点,我们需要添加验证汇总到Checkout.cshtml视图。
6.7 显示总结页面
我们要显示一个确认订单已经处理完毕,感谢他们购买。为Checkout方法添加Completed视图。
7 总结
偶们有一个可以浏览分类和页面的产品分类。一个优雅的购物车,一个简单的结账过程。完好分离的建筑学,以为着偶们可以简单的改变程序任意一部分的功能,而不用担心产生问题或与其他地方不一致。例如,我们可以使用数据库存储订单,并且对它在购物车中,产品分类中,或程序的任何部分,都没有影响。
【Pro ASP.NET MVC 3 Framework】.学习笔记.7.SportsStore:购物车的更多相关文章
- Pro ASP.NET MVC 5 Framework.学习笔记.6.3.MVC的必备工具
每个MVC程序员的军火库中,都有这三个工具:一个依赖注入(DI)容器,一个单元测试框架,一个模拟工具. 1.准备一个示例项目 创建一个ASP.NET MVC Web Application的Empty ...
- Pro ASP.NET MVC 5 Framework.学习笔记.6.4.MVC的必备工具
2.5.创建链式依赖 当你请求Ninject创建一个类型,它检查该类型的依赖是否声明.它也会检查该依赖是否依赖其他类型.如果这里有附加依赖,Ninject自动解决他们,并创建请求的所有类的实例.正是由 ...
- ASP.NET MVC Web API 学习笔记---第一个Web API程序
http://www.cnblogs.com/qingyuan/archive/2012/10/12/2720824.html GetListAll /api/Contact GetListBySex ...
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.11.ASP.NET MVC3的细节:概览MVC项目
书Adam The Definitive Guide to HTML5 Adam Applied ASP.NET 4 in Context and Pro ASP.NET 4 到此为止,我们已经学了为 ...
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.12.ASP.NET MVC3的细节:URLs,Routing和Areas
Adam Applied ASP.NET 4 in Context 1 介绍Routing系统 在引入MVC之前,ASP.NET假定被请求的URLs和服务器硬盘上的文件之间有着直接关系.服务器的任务是 ...
- 【Pro ASP.NET MVC 3 Framework】.学习笔记.9.SportsStore:Securing the Administration Features
1 设置表单身份认证 因为ASP.NET MVC基于ASP.NET平台的核心,所以我们可以使用ASP.NET Form的身份认证,这是保持用户登录轨迹通用的方法.现在介绍最基本的配置. 在Web.co ...
- ASP.Net MVC开发基础学习笔记:一、走向MVC模式
一.ASP.Net的两种开发模式 1.1 ASP.Net WebForm的开发模式 (1)处理流程 在传统的WebForm模式下,我们请求一个例如http://www.aspnetmvc.com/bl ...
- ASP.Net MVC开发基础学习笔记(1):走向MVC模式
一.ASP.Net的两种开发模式 1.1 ASP.Net WebForm的开发模式 (1)处理流程 在传统的WebForm模式下,我们请求一个例如http://www.aspnetmvc.com/bl ...
- ASP.NET MVC 5 系列 学习笔记 目录 (持续更新...)
前言: 记得当初培训的时候,学习的还是ASP.NET,现在回想一下,图片水印.统计人数.过滤器....HttpHandler是多么的经典! 不过后来接触到了MVC,便立马爱上了它.Model-View ...
随机推荐
- mina学习
长连接表示一旦建立了链接,就可以长时间的保持双方的通讯,例如: socket链接,推送平台. 短链接表示建立链接,完成数据的交换之后,就断开链接,例如: http链接. mina 框架是对socket ...
- nsstring字符串重组
// // main.m // 05-字符串重组 // // Created by apple on 14-3-20. // Copyright (c) 2014年 apple. All ri ...
- Hadoop学习(4)-- MapReduce
MapReduce是一种用于大规模数据集的并行计算编程模型,由Google提出,主要用于搜索领域,解决海量数据的计算问题.其主要思想Map(映射)和Reduce(规约)都是从函数是编程语言中借鉴而来的 ...
- 错过C++
曾相识的C++--2008年那是还在学校,接触到了这门语言,可遗憾的是当时,自己没有能静下心好好学习这门语言.所以相识了了半年的c++就这样不见. 如今又相逢,但已经感觉到很陌生,陌生的我们互补相认.
- Nodejs解决2分钟限制
摘要:解决:在nodejs中调用服务,若超过2分钟服务没有返回数据,node会再次请求服务. 加班的日子总算暂时结束了,才发现下午6点钟的天还没有黑!开始我的总结吧... 去年的某个项目用nodej ...
- MySQL 常用函数列表
一.数学函数 select SQRT (2) --取平方根select ABS (-234) --取绝对值select FLOOR (COUNT (*)/5.0) from news --取小于这个小 ...
- 转:Python一些特殊用法(map、reduce、filter、lambda、列表推导式等)
Map函数: 原型:map(function, sequence),作用是将一个列表映射到另一个列表, 使用方法: def f(x): return x**2 l = range(1,10) map( ...
- jsp编写页面时常见错误提示
jsp编写页面时常见错误提示 404-->未部署web应用 500-->代码有问题 无法显示网页-->未启动tomcat webRoot-->URL输入有误 web-inf-- ...
- 变形--位移 translate()
translate()函数可以将元素向指定的方向移动,类似于position中的relative.或以简单的理解为,使用translate()函数,可以把元素从原来的位置移动,而不影响在X.Y轴上的任 ...
- dtree的使用和扩展
相信用过dtree的童靴的不在少数,网络上流传的JS树有很多,例如雪花树MzTreeView,EXT.Struts2出来之后,也有自己的树控件,但是这么多风姿卓约的倩影中,我独爱,独爱dtree那一棵 ...