我们要建造的程序不是一个浅显的例子。我们要创建一个坚固的,现实的程序,坚持使它成为最佳实践。与Web Form中拖控件不同。一开始投入MVC程序付出利息,它给我们可维护的,可扩展的,有单元测试卓越支持的构造精良的代码。一旦我们有了基本的基础设施,我们就能加快。
1 创建解决方案和项目
1.1 创建一个空白解决方案,命名为SportsStore,添加三个项目
Project Name | VS Project Template | Purpose |
SportsStore.Domain | 类库 | 提供域实体和逻辑。通过一个EF创建的repository,配置持久化 |
SportsStore.WebUI | 带Razor的空白MVC3程序 | 扮演程序的UI,提供controllers和views |
SportsStore.UnitTests | Test Project | 为另外两个项目提供单元测试 |
添加引用
Project Name | Tool Dependencies | Project Dependencies |
SportsStore.Domain | None | None |
SportsStore.WebUI | Ninject | SportsStore.Domain |
SportsStore.UnitTest | Ninject Moq | SportsStore.Domain SportsStore.WebUI |
1.2 配置DI容器
我们要用Ninject来创建controllers和handle the DI。如果要这样做,需要创建一个新类,并做一些配置上的改变。
在SportsStore.WebUI中新建一个 Infrastructure 文件夹,然后在里面创建一个 NinjectControllerFactory类。
如果要添加引用类型,可以controle+. 。
现在还没有添加任何Ninject绑定,可以在需要时使用AddBindings方法。我们需要告诉Mvc,我们想要使用NinjectController类,来创建controller object。需要在Global.asax.cs文件中添加声明:
2 开始领域模型
2.1 在MVC程序中,几乎所有的事情都围绕着领域模型,所以它是一个完美开始。因为这是一个电子商务程序,我们需要的最明显的领域实体是Product。
在SportsStore.Domain中新建Entities文件夹,在它里面新建Product类文件。
我们遵循公约,在单独的项目里定义我们的领域模型,这意味着类必须被标记为public。这个公约可以帮助我们保持model从controllers分离。
2.2 创建一个抽象Repository
我们知道我们需要一些从数据库得到Product实体的方式。我们使用repository模式,让持久化逻辑和领域模型实体相分离。目前,我们不用担心如何去实现持久化,但是我们将开始定义一个关于它的接口。
在SportsStore.Domain项目中新建一个Abstract文件夹,在它里面新建一个接口IProductsRepository。
这个接口使用了IQueryable接口,它允许获得一个Product objects的序列,而不用说数据如何获得或存在哪里。使用IProductRepository接口的类,可以获得Product objects,不需要知道任何关于数据从哪里来,它们如何分发。这是repository模式的本质。我们会在以后为该接口添加特性。
2.3 做一个Mock Repository
现在我们定义了抽象接口,我们能实现持久化机制,并hook 它 链接到数据库。为了能够开始写程序的其他部分,现在要创建一个实现了mockde的IProductRepository接口。我们将在NinjectControllerFactory类中的AddBindings方法中做这些。
VS会处理这些生命中的所有新类型的命名空间。
3 显示一个Products列表
3.1 添加Controller
创建一个空Controller
我们添加了一个构造器,携带IProductRepository参数。这回允许Ninject为product repository,在它实例化controller类时,注入依赖。
通过传递一个Product对象的List给View方法,我们提供了框架和数据填充,给强类型的视图。
3.2 添加View
选中创建强类型视图,在Model class中,填入
下拉框中不包含领域对象的枚举类型。View中的model包含一个IEnumerable<Product>意味着我们在Razor中能使用foreach创建列表。
将Price属性使用ToString(“c”)方法转换,它将数字类型的值按照文化设置作为货币渲染。可以在Web.config<system.web>节点中添加一个section,来改变文化设置。
3.3 设置默认路由
在Global.asax.cs中的RegisterRoutes中,设置
4 准备一个数据库
我们依然在用mock IProductRepository返回的测试数据。在我们实现一个真正的repository,我们需要部署一个数据库,并用数据填充它。
我们要用SQL Server作为数据库,并使用EF访问数据库。EF是 .NET ORM框架,它允许我们使用常规的C#对象,操作关系数据库的表,列,行。
在服务器资源管理器中,在数据连接上点右键,创建新sql数据库。
新建Products表,设置ProductID列为主键,标识列,自增1 。
在表中新增一些测试数据
4.1 创建EF Context
EF的4.1版本包含一个很不错的特性,code-first。它让吗我们可以先在model中定义类,然后从这些类生成数据库。
我们使用一个已经存在的数据库,关联我们的model类,使用code-first的变种。
为SportsStore.Domain添加EF引用,下一步是创建一个context类,将我们简单的model关联到数据库。
创建Concrete文件夹,在其中创建EFDbContext类
为了从code-first特性获利,我们需要创建一个类派生自System.Data.Entity.DbContext的类。这个类为每个我们想要操作的表定义了一个属性。属性名为表名,DbSet返回类型参数,指定为EF在表中持久化行的模型。在我们的例子中,属性名是Products,类型参数是Product。我们想让Product模型类型用来持久化Products表中的行。
我们需要告诉EF,如何连接到数据库。通过在SportsStore.WebUI的Web.config添加一个数据库连接字符串。
链接字符串的名字是非常重要的,它必须和context类的名字相匹配,因为它是EF链接我们想要操作的数据库。
4.2 创建Product Repository
现在,我们有一切我们需要的,来用真实的数据实现IProductRepository类。在Concrete文件夹中添加EFProductRepository类
这是我们的repository类。它实现了IProductRepository接口,使用一个EFDbContext的实例,使用EF从数据库里取回数据。你会看到使用EF特性的repository操作起来是如何简单。最后的舞台,是使用真实的数据的mock repository,替换Ninject绑定。
将SportsStore.WebUI中的NinjectControllerFactory的AddBindings方法,改为
这个绑定,告诉Ninject,我们想要创建一个EFProductRepository类的实体,为IProductRepository接口的查询服务。
5 添加页码
显示一定数量的products在一个页面,用户可以一页一页地浏览全部的目录。为了做到这点,我们需要添加一个参数给controller中的List方法。
此处,方法的参数必须和路由表中 controller action id处的一样,不然取不到。int?加上问号后,是可空类型。为可空类型赋默认值。
此处的方法的返回类型为ViewResult,它包含Model,后面单元测试中会用到。如果使用ActionResult,它不包含Model。
在后面,我们会将其替换为更好的分页机制。当不指定页码时,默认是第一页。Linq使得分页非常简单。在List方法中,我们从repository获得Product对象,并使用主键排序,跳过起始页之前的所有products,然后取前pageSize个products。
5.1 对分页使用单元测试
我们可以创建mock repository,当调用List方法,去请求指定页时,将它注射到ProductController类的构造函数中,对分页特性使用单元测试。然后可以比较我们得到的Product对象。
5.2 显示页面链接
5.2.1 添加视图模型
要支持HTML helper,我们要传递信息给view,如总共有多少页,当前是第几页,repository中的products总共有多少。要做到这些,最简单的方法是创建一个view model,在SportsStore.WebUI的Models文件夹中,新建PagingInfo类
View Model不是我们领域模型的而一部分。它只是方便我们在controller和view之间传递数据的类。为了强调这点,我们把它放在SportsStore.WebUI中,让他和领域模型的类分离。
5.2.2 添加HTML Helper Method
现在我们有了视图模型,我们可以实现HTML helper方法,它被叫做PageLinks。在SportsStore.WebUI中新建HtmlHelpers文件夹,添加新的静态类PagingHelpers。
PageLinks扩展方法,使用PagingInfo对象提供的信息,生成一组page links的HTML。Func参数,提供了传递委托的能力,用来生成显示在其他页面上的链接。
5.2.3 对生成的page links使用单元测试
为测试PageLinks helper方法,我们使用测试数据,调用它,并将它产生的结果,和我们期待的HTML作比较。
测试正式了helper方法输出包含两个引号的字符串值。C#能完美地胜任处理这样的字符串,只要我们记得在字符串前加@,并使用两组双引号,来替代一组双引号。我们也必须记住不能打破字符串到单独的行,除非我们比较的字符串也是同样的破碎。例如,我们在test方法中包裹的字符串有两行,因为页面的宽度太窄。偶们没有添加新航符号,如果我们这样做,测试会失败。
在Razor视图中,要引用扩展方法,我们必须在Web.config中添加配置,或者在view中直接添加@using声明。Razor MVC项目里有两个Web.config文件:主文件,在根目录下。View目录下的是Veiw-Spacific。这里我们要改变View目录下的配置文件。
每个Razor要用到的命名空间,都需要通过这种方式,或直接在view中使用@using 声明。
5.2,4 添加视图模型数据
我们还没有完全准备好使用HTML helper方法。我们也需要给View提供PagingInfo视图模型类的实例。为了做到这点,我们可以使用View Data或View Bag特性,但是我们需要将它转换为适当的类型。
我们更想将从controller发送到view的数据的所有数据,包装成一个单独的视图模型类。为了做到这点,添加一个ProductListViewModel类到Models文件夹。
现在偶们需要更新List方法,使用ProductsListViewModel类,将Products要显示的细节,和分页的细节,来提供给视图。
这个改变,将传递ProductsListViewModel对象作为模型数据,发送给view。
5.2.5 分页模型视图数据的单元测试
因为List action方法的返回的模型变了,所以需要对Can_Paginate进行修改。
现在需要修改List.cshtml,来处理新的视图模型类型。
5.2.6 为什么不直接使用gridview
如果用过ASP.NET,可以使用Web Form的GridView控件,直接关联到Products数据库表上。
首先,我们建立了一个坚固的,可维护的建筑,包括适当的关注点分离。不像简单地使用GridView,我们没有直接将UI和数据库组合在一起,这种方式能够快速得到结果,但随着时间的推移,会痛苦和不幸。
第二,偶们创建了单元测试,它允许我们用原生的方法,验证程序的行为,这在Web Form的GridView控件中是不可能的。
最后,记住这些章节已经创建了程序的底层基础设施。我们只需要定义和实现repository一次,我们就能快速且容易地创建和测试新特性。
5.3 改进URLs
我们依然使用传递给夫妻的查询字符串现在在page links中。我们能做的更好,指定一个URLs组成的方案。显示效果像最下面那样
因为使用ASP.NET routing特性,所以能很简单地实现。它允许我们在Global.asax.cs中添加一个新的路由到RegisterRoutes方法。
可以发现,Url.Action方法生成的链接也变成了以上格式。
6 Content的样式
在_Layout.cshtml文件中,新增如下代码
Razor不能自动地识别 ~ ,将其作为程序的根。所以我们要使用helper的@Url.Content方法。
6.1 创建局部视图
作本章的结束,我们将重构程序,以简化List.cshtml。我们将创建局部视图,它是嵌入在其他view中的片段。局部视图被包含在它自己的文件中,可以通过view在读使用,帮助我们减少复制,尤其是在你需要在很多地方渲染相同类型的数据。
要添加局部视图,在/View/Shared文件夹上右键,选择新建视图ProductSummary,选择Product类作为模型,勾上作为局部视图的选项。点击添加后,在Views/Shared/ProductSummary.cshtml。局部视图和常规视图很相似,但是当我们访问它时,它渲染了一个HTML片段,而不是整个HTML文档。
然后使用局部视图更新List.cshtm。
调用Html.RenderPartial helper方法,参数是局部视图的名字和视图模型对象。
RenderPartial方法不像其他helper method返回HTML标记。它将content直接写入response流中。这是我们必须以完整的C#行,使用分号,调用它的原因。这是稍微更有效率,比从局部视图缓存HTML渲染。如果你想要坚持始终如一的语法,可以使用Html.Partial方法,它完全和RenderPartial方法一样,但是能不加分号使用。像这样切换到局部视图是个很好的实践。
7 总结
现在偶们有了领域模型的开始,使用Sql server和EF返回的Product repository。我们有了一个简单的controller,它能产生products的分页,我们设置了DI和一个简洁而友好的URL方案。
下章,我们会有完全面向客户的特性:分类导航,购物车,结账流程。
关于CSS的书籍
- Pro CSS and HTML Design Patterns by Michael Bowers (Apress, 2007)
- Beginning HTML with CSS and HTML by David Schultz and Craig Cook (Apress, 2007)
【Pro ASP.NET MVC 3 Framework】.学习笔记.5.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 ...
-
【Pro ASP.NET MVC 3 Framework】.学习笔记.7.SportsStore:购物车
3 创建购物车 每个商品旁边都要显示Add to cart按钮.点击按钮后,会显示客户已经选中的商品的摘要,包括总金额.在购物车里,用户可以点击继续购物按钮返回product目录.也可以点击Check ...
-
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 ...
随机推荐
-
yii框架安装心得
最近在学习yii框架, 现在将遇到的一些问题和解决方法写出来与大家分享. yii框架的安装: 下载yii框架之后, 打开文件运行init.bat文件, 如果闪退就打开php的扩展(php_openss ...
-
重新开源UDS
这个题目起得很纠结. 因为很多人都知道UDS本来就是开源,我只不过改了一些东西,然后重新发布,所以不算重新开源. 要说重新发布也不对.因为老早这东西就发布了. 最后我想,这个东西已经很久没更新过了,也 ...
-
centos 7 升级后yum install出现Exiting on user cancel
centos 7 升级后yum install出现Exiting on user cancel centos 7.x升级后用yum install进行安装时经常出现Exiting on user ca ...
-
Java:Collection集合类
特点:只能用于存储对象,集合长度时可变的,集合可以存储不同类型的对象. 数组和集合类同时容器,不同的是: 1.数组虽然也可以存储对象,但是长度是固定的:集合长度时可变的. 2.数组中可以存储基本数据类 ...
-
Github干货系列:C++资源集合-
Awesome CPP,这又是一个 Awesome XXX 系列的资源整理,由 fffaraz 发起和维护.内容包括:标准库.Web应用框架.人工智能.数据库.图片处理.机器学习.日志.代码分析等. ...
-
Java程序设计与数据结构导论--读后感
与我前面所读的<Java7基础教程>相比,此书不适合自学,更适合作为教材使用. 虽然此书完整覆盖了Java的知识点和数据结构的基础问题,并且对每个部分都做了基本说明.但是因为没有深入展开, ...
-
Python 学习笔记6 变量-元组
我们在上一篇中了解了变量list(列表), 今天我们来介绍下元组.元组是由括号和逗号,组织起来的一个元素的集合.和list不同的是,它其中的元素是不能被修改的,和其他语言中的常量相类似. 需要注意的是 ...
-
Python学习第十二篇——切片的使用
Python中使用函数切片可以创建副本,保留原本.现在给出如下代码 magicians_list = ['mole','jack','lucy'] new_lists = [] def make_gr ...
-
BZOJ.3218.a + b Problem(最小割ISAP 可持久化线段树优化建图)
BZOJ UOJ 首先不考虑奇怪方格的限制,就是类似最大权闭合子图一样建图. 对于奇怪方格的影响,显然可以建一条边\((i\to x,p_i)\),然后由\(x\)向\(1\sim i-1\)中权值在 ...
-
用RestTemplate调取接口,取得返回数据,携带header,动态拼接url ,动态参数
记录我自己的工作 get 请求 ,携带 请求头 header (token) url 根据参数 动态拼接 参数 放入 map 动态拼接 private String lclUrl = &quo ...