我们要建造的程序不是一个浅显的例子。我们要创建一个坚固的,现实的程序,坚持使它成为最佳实践。与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中,填入

IEnumerable<SportsStore.Domain.Entities.Product>

下拉框中不包含领域对象的枚举类型。View中的model包含一个IEnumerable<Product>意味着我们在Razor中能使用foreach创建列表。

}

将Price属性使用ToString(“c”)方法转换,它将数字类型的值按照文化设置作为货币渲染。可以在Web.config<system.web>节点中添加一个section,来改变文化设置。

<globalization culture="fr-FR" uiCulture="fr-FR" />

3.3 设置默认路由

在Global.asax.cs中的RegisterRoutes中,设置

new { controller = "Product", action = "List", id = UrlParameter.Optional }

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添加一个数据库连接字符串。

</connectionStrings>

链接字符串的名字是非常重要的,它必须和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目录下的配置文件。

<add namespace="SportsStore.WebUI.HtmlHelpers"/>

每个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进行修改。

Product[] prodArray = result.Products.ToArray();

现在需要修改List.cshtml,来处理新的视图模型类型。

</div>

5.2.6 为什么不直接使用gridview

如果用过ASP.NET,可以使用Web Form的GridView控件,直接关联到Products数据库表上。

首先,我们建立了一个坚固的,可维护的建筑,包括适当的关注点分离。不像简单地使用GridView,我们没有直接将UI和数据库组合在一起,这种方式能够快速得到结果,但随着时间的推移,会痛苦和不幸。

第二,偶们创建了单元测试,它允许我们用原生的方法,验证程序的行为,这在Web Form的GridView控件中是不可能的。

最后,记住这些章节已经创建了程序的底层基础设施。我们只需要定义和实现repository一次,我们就能快速且容易地创建和测试新特性。

5.3 改进URLs

我们依然使用传递给夫妻的查询字符串现在在page links中。我们能做的更好,指定一个URLs组成的方案。显示效果像最下面那样

http://localhost/Page2

因为使用ASP.NET routing特性,所以能很简单地实现。它允许我们在Global.asax.cs中添加一个新的路由到RegisterRoutes方法。

}

可以发现,Url.Action方法生成的链接也变成了以上格式。

6 Content的样式

在_Layout.cshtml文件中,新增如下代码

</div>

Razor不能自动地识别 ~ ,将其作为程序的根。所以我们要使用helper的@Url.Content方法。

6.1 创建局部视图

作本章的结束,我们将重构程序,以简化List.cshtml。我们将创建局部视图,它是嵌入在其他view中的片段。局部视图被包含在它自己的文件中,可以通过view在读使用,帮助我们减少复制,尤其是在你需要在很多地方渲染相同类型的数据。

要添加局部视图,在/View/Shared文件夹上右键,选择新建视图ProductSummary,选择Product类作为模型,勾上作为局部视图的选项。点击添加后,在Views/Shared/ProductSummary.cshtml。局部视图和常规视图很相似,但是当我们访问它时,它渲染了一个HTML片段,而不是整个HTML文档。

</div>

然后使用局部视图更新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一个真实的程序的更多相关文章

  1. Pro ASP.NET MVC 5 Framework.学习笔记.6.3.MVC的必备工具

    每个MVC程序员的军火库中,都有这三个工具:一个依赖注入(DI)容器,一个单元测试框架,一个模拟工具. 1.准备一个示例项目 创建一个ASP.NET MVC Web Application的Empty ...

  2. Pro ASP.NET MVC 5 Framework.学习笔记.6.4.MVC的必备工具

    2.5.创建链式依赖 当你请求Ninject创建一个类型,它检查该类型的依赖是否声明.它也会检查该依赖是否依赖其他类型.如果这里有附加依赖,Ninject自动解决他们,并创建请求的所有类的实例.正是由 ...

  3. ASP.NET MVC Web API 学习笔记---第一个Web API程序

    http://www.cnblogs.com/qingyuan/archive/2012/10/12/2720824.html GetListAll /api/Contact GetListBySex ...

  4. 【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 到此为止,我们已经学了为 ...

  5. 【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和服务器硬盘上的文件之间有着直接关系.服务器的任务是 ...

  6. 【Pro ASP.NET MVC 3 Framework】.学习笔记.9.SportsStore:Securing the Administration Features

    1 设置表单身份认证 因为ASP.NET MVC基于ASP.NET平台的核心,所以我们可以使用ASP.NET Form的身份认证,这是保持用户登录轨迹通用的方法.现在介绍最基本的配置. 在Web.co ...

  7. 【Pro ASP.NET MVC 3 Framework】.学习笔记.7.SportsStore:购物车

    3 创建购物车 每个商品旁边都要显示Add to cart按钮.点击按钮后,会显示客户已经选中的商品的摘要,包括总金额.在购物车里,用户可以点击继续购物按钮返回product目录.也可以点击Check ...

  8. ASP.Net MVC开发基础学习笔记:一、走向MVC模式

    一.ASP.Net的两种开发模式 1.1 ASP.Net WebForm的开发模式 (1)处理流程 在传统的WebForm模式下,我们请求一个例如http://www.aspnetmvc.com/bl ...

  9. ASP.Net MVC开发基础学习笔记(1):走向MVC模式

    一.ASP.Net的两种开发模式 1.1 ASP.Net WebForm的开发模式 (1)处理流程 在传统的WebForm模式下,我们请求一个例如http://www.aspnetmvc.com/bl ...

随机推荐

  1. 给你完美浪漫的七夕,APICloud送你双人电影票!

    我一直觉得“幸福的感觉” 就像存款 留着以后用 会幸福感爆棚 于是,我一直习惯于等等,再等等 以为那样就会很幸福 直到有一天,突然发现,在我构想的未来中,总是有你 世界那么大,我只在乎你 世界那么长, ...

  2. ubuntu网络配置

    本文介绍ubuntu中的网络配置方法. ubuntu需要在/etc/network/interfaces文件中设置网络配置,基本的格式如下: auto <网卡名> iface <网卡 ...

  3. ucenter 通信成功后 apps.php无误后 该做的事

    比如你的系统有个登陆功能,在本系统登陆之前要先执行如下代码,先把apps.php里的所有站点先登陆一遍 <meta charset="utf8"> <?php i ...

  4. Android适配(屏幕适配、国际化适配)-转

    首先来说一下Android的屏幕适配: 关于Android屏幕的一些基本概念知识,自行充电..在此只介绍实际开发过程中的使用 1.说到Android的屏幕适配,首当其冲的就是图片的适配 图片适配遵循两 ...

  5. Vue.2.0.5-过渡效果

    概述 Vue 在插入.更新或者移除 DOM 时,提供多种不同方式的应用过渡效果.包括以下工具: 在 CSS 过渡和动画中自动应用 class 可以配合使用第三方 CSS 动画库,如 Animate.c ...

  6. 可复用的js效果

    以下案例的html以及css代码 <!doctype html> <html lang="zh-hans"> <head> <meta c ...

  7. 清除mstsc远程登录记录

    这里提供一串清除mstsc远程登录记录的命令 @echo off set TMP1=%cd%echo %TMP1%reg delete "HKEY_CURRENT_USER\Software ...

  8. Oracle 监听器日志文件过大导致监听异常

    Oracle 监听器日志文件过大导致监听异常 db版本:11.2.0.1 os版本:windows2008 现象: 应用异常,无法连接数据库.登陆数据库服务器,查看监听已经断掉.尝试重启监听,重启失败 ...

  9. update kernel

    1,version 2,command First, verify the current kernel version: $ uname -r 2.6.32-358.el6.x86_64 Befor ...

  10. fetch the words from url

    python code: import time,urllib fid=open('Friedrich Nietzsche Classic Words.txt','w') #1st ss='http: ...