注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Lucifer 的博客

网易英超评论专栏

 
 
 

日志

 
 
 
 

ASP.NET MVC Framework Part 1 (三)  

2008-05-06 23:03:22|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
建立我们的数据模型

我们现在已经拥有了一个ProductsController类以及三个action方法来准备处理接收到的web request. 我们的下一步就是建立一些能够帮助我们和数据库打交道的类来接收需要的数据.

在MVC的世界,模型(models)在程序中是负责保持状态的组件. 而在web应用当中这种状态通常被保存在数据库当中(例如: 我们可以让一个Product object来代表SQL数据库当中名为Products的table).

ASP.NET MVC Framework 使得你可以使用你想要的数据访问方式或者框架来取得和管理你的模型. 如果你想使用 ADO.NET DataSets/DataReaders (或者是建立在他们之上的抽象),OK. 如果你更喜欢使用一种ORM框架,比如NHibernate, LLBLGen, WilsonORMapper, LINQ to SQL/LINQ to Entities ,也没有问题.

为了我们这个电子商店的例子程序我准备使用在.NET 3.5和VS 2008中集成的LINQ to SQL ORM框架. 你可以在下面的blog里面去深入学习 LINQ to SQL :tutorial series ,Part1, Part2, Part3Part4.

我将右击VS中我的MVC工程文件"Models" 子目录,选择"Add New Item"选项, 添加一个LINQ to SQL模型.  在LINQ to SQL ORM designer当中我定义了三个数据模型类分别映射SQL Server Northwind sample database 当中的Categories, Products, 和 Suppliers 这三个table( Part 2 of my LINQ to SQL series):

image008

一旦我们定义了我们的LINQ to SQL 数据模型类, 我将会在我的Models 目录下添加一个新的NorthwindDataContext的部分类:

image009

在这个部分类中我将会定义一些辅助方法来封装一些LINQ表达式,我们用这些表达式从数据库当中取得产品种类列表,某个特定种类当中的所有产品,以及一个特定产品的详细信息:

image010

这些辅助方法能够使我们方便得取得ProductsController所需要的数据模型object (不用在控制器类内部写LINQ表达式了):

image011

我们现在拥有了ProductsController类功能实现所需要的所有数据code/object. 

 完成我们的ProductsController类的实现

在一个MVC架构基础的应用程序中控制器负责处理接收到的request和用户的输入以及互动操作, 并在他们的基础上执行适当的程序逻辑(取得和更新存储在数据库里面的模型数据等等).

控制器一般不为一个request生成特定的HTML响应Controllers . 生成HTML响应的任务是由程序内部的“视图”组件所负责的- 它以与控制器分离的类/模板的方式实现. 视图试图将整个的焦点集中在封装表现层逻辑上, 并且不包含任何程序逻辑以及从数据库中取得数据的代码(所有的程序逻辑应该被控制器所处理).

在典型的MVC web工作流当中, 控制器action方法将会处理接收到的web request, 使用传递进来的参数去执行适当的程序逻辑代码, 取得或者更新自数据库生成的数据模型object, 然后选择一个相应的视图将其作为UI响应返回给客户端的浏览器.  作为返回视图的一部分, 控制器会显式地传入视图所需要的数据和变量声明以便能够返回相应的视图响应:

image012

你可能会问- 像这样将控制器和视图分离的好处是什么?  为什么不将他们放在一个相同的类里面呢?  其主要动机是将你生成UI的代码和你的应用程序/数据逻辑分离.这能够将你应用程序/数据逻辑从你UI逻辑中隔离开来以更便于进行单元测试.  这还能使得你的应用程序更加容易维护- 因为这会使得你在视图模板里添加偶尔的应用程序/数据逻辑变得更加困难.

当实现我们ProductsController控制器内三个action方法的时候, 我们将会使用传入的URL参数来从数据库中取得所需的模型, 然后选择一个视图组件来发送一个相应的HTML响应.  我们将会使用控制器基类里面的RenderView() 方法来发送我们想使用的视图, 我们也可以显式地传递我们在发送视图响应时候所需要的数据.

下面是我们ProductsController的最终实现:

image013

注意我们在以上action方法里面所使用代码行数是相当少的 (每个两行).  这部分是由于MVC framework 已经自动为我们处理了URL参数的转换逻辑 (把我们从需要写一大堆代码的困境中解救出来). 另外也是因为从业务的观点来看产品浏览机能也是相对比较简单的 (action方法全以只读功能方式显示). 

但是在一般情况下,你将会经常发现你有看起来比较单薄的控制器类– 意味着控制器方法都是由相对简洁的action方法组成 (少于10行代码). 这通常是一个很好的迹象,能够使得你能够清晰地封装你的数据逻辑.

单元测试我们的ProductsController

你可能会很奇怪我们的下一步居然是测试我们的程序逻辑和功能。你可能会问:这怎么可能?我们还没有实现我们的视图呢,现在我们的应用程序根本不会生成哪怕半点的HTML Tag。好吧,我得告诉你这就是MVC框架吸引人的地方之一,我们可以在不依赖视图生成逻辑的情况下单元测试我们应用程序的控制器和模型逻辑就像你将会看到的那样我们甚至可以在创建我们的视图之前进行单元测试。

为了单元测试ProductsController类,我们得继续工作。当我们创建我们的ASP.NET MVC应用程序的时候Visual Studio曾经自动创建了一个Test Project ,现在我们将一个名为ProductsControllerTest的类加到那里面去。

image001

然后我们定义一个简单的单元测试,让它来测试我们ProductsController 的Detail action:

image002

ASP.NET MVC framework本身就被特别设计成有利于单元测试.  Framework内部所有的核心API以及 contracts 都是接口,提供了易于注入和自定义的可扩展点 (包括使用类似Windsor, StructureMap, Spring.NET, 和 ObjectBuilder的控制反转容器).  开发者们可以使用内建的伪造类,或者使用任意.NET伪造类型构成的framework来模拟适合于我们测试版本的MVC相关object.

在以上的单元测试当中, 你可以看到我们在调用Detail() action方法之前,如何在我们的ProductsController类之上,注入一个"视图工厂(ViewFactory)" 的傀儡实现.  通过这个方法我们重写了负责创建和生成视图的默认视图工厂(ViewFactory). 我们能够使用这个测试用视图工厂去将测试从我们的ProductController类的 Detail action 行为中剥离开来 (还可以不用去执行一个实际的视图).  注意我们调用Detail() action方法之后可以在使用三个断言来在其内部是不是发生了正确的行为(尤其是这个action是否取得了正确的Product object然后将其传递给了正确的视图).

由于我们能够伪造和模拟MVC Framework当中的任意object (包括IHttpRequest 和 IHttpResponse objects), 你没有必要在实际的web-server 的环境当中跑单元测试. 取而代之的是你可以在一个普通的class library内部创建ProductsController 然后直接测试它.  这会显著的加速我们单元测试的执行,同时简化了原本配置和执行的冗长过程.

如果我们使用Visual Studio 2008 IDE的话, 我们可以很容易地追踪我们测试执行的成败 (这个功能现在已经内建在 VS 2008 Professional中了):

image003

我认为你将会发现ASP.NET MVC Framework使得单元测试的编写更加简单,并且对测试驱动的开发流程(TDD workflow)提供了很好的支持.

使用视图生成UI

我们完成了电子商店当中产品浏览功能的应用程序逻辑的实现和测试.  现在我们需要为它来实现HTML UI

image004

我们将调用RenderView() 方法,这样的话就能在内部使用我们的ProductsController action方法所提供的视图相关的obeject,通过这个方法来实现负责生成正确UI的视图:

image005

在以上这个代码的例子当中,RenderView 方法的第一个参数"Categories"表示我们将会调用的视图的名字, 第二个参数是我们想要传给视图的category 的清单,我们需要将它作为数据源去生成正确的HTML UI.

ASP.NET MVC Framework支持使用任何的模板引擎去帮助生成UI (包括已经存在的诸如NVelocity, Brail 的模板引擎- 还有你自己编写的新引擎).  ASP.NET MVC Framework默认使用ASP.NET当中既存的 ASP.NET Page (.aspx), Master Page (.master), 以及UserControl (.ascx)模板. 

我们将会使用内建的ASP.NET 视图引擎去实现我们电子商店的应用UI.

定义Site.Master 文件

因为我们准备为我们的站点建立很多的页面, 所以我们以定义一个master page作为我们UI工作的开端,通过使用master page我们可以将我们网站中网页需要共享使用的页面布局封装起来. 我们将在我们工程的\Views\Shared目录下一个名为"Site.Master" 文件当中完成我们的工作:

image006

我们可以为我们的网站参照一个CSS的style sheet去封装所有的style, 然后使用master page去定义网站页面整体的布局, 同时标识一些我们之后可以使用拥有特定内容的页面去填充的placeholder 区域.  我们可以选择新的VS 2008 designer中一些很cool的特性去完成这项工作 (包括 HTML split-view designer, CSS Authoring, 和 嵌套Master Page支持).

image007

理解/Views 的目录结构

当你使用默认的Visual Studio 来创建ASP.NET MVC 工程的时候, 它会自动在"Views"目录下创建一个名为"Shared" 的子目录. 这是我们推荐的保存(在我们应用程序当中需要在不同控制器当中共享的)Master Pages, 用户控件和视图 的地方.

当为某一个单独的控制器建立视图的时候, ASP.NET MVC会默认的将其保存在\Views目录下的一些子目录当中. 默认情况下这个子目录的名字需要和控制器的名字相对应.  例如, 由于控制器类的名字叫做"ProductsController", 我们将会把其相关视图默认的保存在\Views\Products 这个子目录下:

image008

当我们在特定的控制器当中调用RenderView(string viewName) 方法的时候, MVC framework 将会自动地在\Views\ControllerName目录下面查找相应的 .aspx 或者 .ascx 视图模板, 然后如果它无法找到合适的视图模板的话它就会去\Views\Shared 下面继续查找:

创建一个Categories 视图

我们可以在Visual Studio 当中我们的项目的Products目录上面点击右键,选择 "Add New Item" ,然后在菜单选项中选择"MVC View Page"这个模版,通过这个方式为我们的控制器类添加视图.这将创建一个新的.aspx页面,我们也可以将它关联到我们的Site.Master master page 上面去,以取得网站整体页面的统一外貌和风格(就像通过master page你可以取得完全的WYSIWYG设计器的支持):

image009

当使用MVC的模式去新建应用程序的时候, 你需要让你的视图代码尽可能的简单, 并且确信视图代码纯粹只是和生成UI有关.  应用程序和数据取得的逻辑应该在控制器类当中编写.  在调用RenderView方法的时候,控制器类能够选择将生成视图所需要的数据object传给视图类. 比方说,以下在我们的ProductsController类的 Categories action 方法当中我们将一组Category的集合清单传递给了Categories视图:

image010 

MVC视图页面默认继承自System.Web.Mvc.ViewPage这个基类, 其提供了许多MVC特定的helper方法与属性让我们可以使用来构建我们的UIwhich.  这些视图页面的属性之一就是"ViewData, 它提供了一些特定视图数据的访问服务,而这些特定的视图数据正是控制器类作为参数传递到RenderView() 方法的.

在你的视图当中你可以选择两种方式来访问这些视图数据,一种是晚绑定方式(late-bound),一种是强类型方式(strongly typed ). 如果你的视图是继承自 ViewPage的话, 那么视图数据属性会被定义晚绑定的数据字典(late-bound dictionary).而如果你的视图是继承自泛型基类ViewPage的话 - T表示控制器传给视图的数据object的类型 - 那么视图数据属性将会是强制地匹配你控制器传入数据的同样类型,这就是强类型方式.

比如所, 我们的Categories 视图后台的类如下所示示继承自 ViewPage - 这里我表示T是一组Category object的清单:

image010-2

这意味着当实际编码的时候我在我的视图代码内能够得到完全的类型安全,智能化以及编译时的检测:

image011

生成我们的Categories视图:

如果你能记得在这篇blog开端时候的那些屏幕截图的话,我们需要在我们的Categories视图当中显示一组产品种类的清单:

image012

我可以在我的Categories视图实现当中使用两种HTML UI生成代码方法中的一种: 1) 在.aspx 文件内使用内联代码, 或者 2) 在.aspx 文件内使用ASP.NET的服务器控件然后从我的后台代码中进行数据绑定.

生成方法一:使用内联代码
ASP.NET页面, 用户控件以及Master页面现在都支持使用和的标识来将生成的代码嵌入到HTML markup当中. 我们可以在我们的 Categories 视图当中使用这个技术来方便地放一个foreach的循环来生成目标HTML种类清单:

image013

VS 2008 在代码编辑器当中对于VB和C#提供了完全的代码智能感知支持. 这意味着我们能够对传到我们视图的Category 模型的object数据进行智能化输入:

image014

VS 2008 也提供对于内联代码的完全调试支持(允许我们使用它去设置断点以及动态地改变视图当中的任何东西):

image015

生成方法二: 使用服务器控件

ASP.NET页面, 用户控件以及Master页面现在都支持使用声明豪的服务器空间去封装HTML UI的生成.  不使用如上上面所说的内联代码, 我们可以使用.NET 3.5当中新的控件 来生成我们目标清单的UI:

image016

注意上面ListView控件是如何封装生成一组值以及处理清单当中没有任何项目的情况的(使得我们不用在markup当中去写if/else语句了).  我们能够使用如下的后台代码将一组category object数据绑定到listview控件:

image017

重要: MVC的世界当中我们在我们的视图后台代码类中只需要放入生成逻辑(不用放任何的程序和数据逻辑). 注意上面我们仅仅放入了为 ListView控件制定强类型的ViewData的逻辑我们的ProductsController 控制器类才是我们从数据库取得Categories清单的地方- 不是视图

这个在视图模版中使用ListView服务器控件的版本将会同我们前一个使用内联代码的版本一样都能生成HTML.  我们在页面上没有一个

的控件, 没有视图状态( viewstate), ID 值或者其他markup. 所以我们拥有的是友好的纯粹的CSS HTML:

image018

Html.ActionLink方法

一件你必须注意到的事情是不管在内联代码实现的版本里面还是服务器控件实现的版本里面都有着调用Html.ActionLink 方法的代码片断:

image019

这个Html object是 ViewPage 基类上的helper 属性, 而ActionLink 方法是这个object的一个辅助方法,它的作用是动态的生成可以链接回控制器action方法的HTML超链接. 如果你看到了以上的HTML输出图片, 你就可以看到一些通过这个方法产生的HTML输出例子:

http://weblogs.asp.net/Products/List/Beverages"<Beverages

我正在使用的Html.ActionLink helper 方法的声明如下:

string ActionLink(string text, object values);

其第一个参数表示了产生的超链接的内部文本内容 (例如: text goes here).  第二个参数表示了一个匿名的object,其实就是一组值按顺序排列好的集合,我们可以使用去生成实际 (你可以将这认为是 一个产生数据词典的更清晰方法). 在以后的blog当中我还是就URL路由引擎是如何工作的介绍更多的细节.  然而作为一个摘要,我能告诉你的是你不仅可以使用URL路由系统去处理接收到的URL访问,而且可以利用它去生成你想要对外输出的HTML. 如果你有类似如下的路由规则:

///

然后在ProductController的Category 视图里面写下了如下代码:

ActionLink 方法会使用你应用程序当中的URL映射规则去将你的参数值自动转换成你的生成输出:

http://weblogs.asp.net/Products/List/Beverages"<Click Me to See Beverages

这可以让你的应用程序内部更容易的生成URL以及使用AJAX回调到你的控制器.  这也意味着你可以在一个地方更新你的URL路由规则,然后使得你应用程序中的所有代码自动地在接收URL处理和生成URL输出时候使用这一更新.

特别注意: 为了促进可测试性, 现在的MVC framework 并不支持在你的视图里面直接postback回服务器控件 .  取而代之的是 ASP.NET MVC应用程序生成超链接并且通过AJAX回调到控制器方法 – 然后使用视图(或者在其内的任何服务器控件) 去单独生成输出.  这能够让你的视图逻辑保持最小化并将注意力集中在生成上, 另外你也可以方便的单元测试你的控制器方法,在不依赖视图的情况下验证你所有的程序和数据逻辑行为. 我会在以后的blog中介绍更多相关内容.

Summary

第一篇blog的篇幅真的相当长,但是希望给大家提供一个概览,让大家看看新的ASP.NET MVC Framework 的各个组件之间是如何协同工作的, 以及你可以如何使用它去建立你自己想要的应用. ASP.NET MVC的第一个公共预览版将会在几周后发布(下载), 你可以下载安装它去做所有我上面介绍的步骤.

由于许多MVC继承的概念 (尤其是概念分离的主张) 对于许多阅读这篇文章的人来说可能是陌生的东西, 希望这篇blog也能展示我们开发了相当一段时间的ASP.NET MVC 是如何适应既存的 ASP.NET, .NET, 以及Visual Studio 特性的.  你可以使用.ASPX, .ASCX 和 .MASTER 文件以及 ASP.NET AJAX 去创建你的ASP.NET MVC 视图.  现在的ASP.NET 当中的有些非UI特性,比如Forms 身份验证, Windows 身份验证, Membership, Role, Url Authorization, Caching, Session State, Profiles, Health Monitoring, Configuration, Compilation, Localization, 以及HttpModules/HttpHandlers ,全部这些都安全支持MVC 模型.

如果你不喜欢MVC 模型或者发现它不符合你的开发风格, 你也可以不必使用它. 这完全可选的 - 它也不会取代既存的WebForms 页面控件模型.  不管是WebForms还是MVC都将会得到完全支持并且在将来不断改进.  如果你需要的话你也可以建立一个单独的应用程序,让其一部分使用WebForms 而另外一部分使用MVC.

如果你真正喜欢你从以上MVC的blog里面看到的东西 (或者被激起了兴趣想要学习更多), 请你在未来的时间内对我的blog保持注意.  我将会介绍更多的MVC概念并且通过使用它来建立我们的电子商店应用程序以展示其更多的特性

.

希望这能有用,

Scott

  评论这张
 
阅读(626)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017