引用原文地址:http://weblogs.asp.net/scottgu/archive/2007/11/13/asp-net-mvc-framework-part-1.aspx
来自Scott Gu的blog中ASP.NET MVC系列,以下是译文
ASP.NET MVC Framework (Part 1)
两周之前我写了一个ASP.NET MVC Framework Overview的blog,上面说我们将很快就把它当为一个可选的特性进行支持。它提供了一个结构化的模型,在程序内部实现了清晰的概念分离,而且能够就你的代码进行更容易的单元测试,并支持测试驱动的开发流程(TDD).它还能够帮助你更好的发布你的程序,比如对URL和最终响应的HTML进行更多的控制。
由于我最近一直在回答那些渴望学习更多的人们的很多问题,既然大家给予如此程度的关注,我觉得我应该去写一些新的BLOG来介绍如何去使用这个framework,这第一篇就是近几周以内我会陆续发布的其中之一。
一个简单的电子商店的应用例子
我将会以一个简单的电子商店的应用程序为例,来说明ASP.NET MVC Framework是如何工作的. 今天我将会实现产品的列表/浏览功能
我们将会构建一个商店的web应用程序,当终端用户在这个网站当中访问/Products/Categories的URL的时候,使得他们能够浏览产品种类的列表:
当用户点击如图所示网页中任意产品种类的超链接的时候, 页面将会以/Products/List/CategoryName的URL迁移到某一个产品种类的页面,以列出这个特定的种类下所有的产品。
当用户点击一个单独产品的时候,浏览器将以/Products/Detail/ProductID的URL迁移到某一个产品的详细信息页面- 以显示你所选择的特定产品更多的细节。
我们将会使用新的ASP.NET MVC Framework 来构建以上全部功能.这将使得我们在应用程序的不同组件之间保持“清晰的概念分离”, 使我们更容易地进行单元测试集成和测试驱动的开发
创建一个新的ASP.NET MVC 应用程序
Visual Studio(下载安装了ASP.NET MVC Framework后)包含ASP.NET MVC Project 的模板,能够让我们方便地创建一个新的web应用程序。
在菜单项目里面选择 File->New Project 然后选择"ASP.NET MVC Web Application" 的模板来创建一个新的web应用程序
Visual Studio 将会默认地为你创建一个新的solution 并向内添加两个 project. 第一个project 是你用来实现你的程序的web project. 第二个是能够让你写单元测试代码的测试project:
使用ASP.NET MVC Framework的时候,你能够使用任意的单元测试框架 (包括 NUnit, MBUnit, MSTest, Xunit和其他). 现在VS 2008 Professional 对 MSTest提供了内建测试project的支持, 当你使用VS 2008时,我们默认的ASP.NET MVC project 模板也会自动地创建这些project中的一个。
我们也将会提供对于NUnit, MBUnit 以及其他单元测试框架支持的相干下载, 所以如果你更愿意使用那些的话你也能够通过一个简单的点击来创建你的程序并且快速集成一个你想要的测试project。
理解项目的文件夹结构
ASP.NET MVC 应用程序会默认地创建一个三层目录的结构:
-
/Controllers
-
/Models
-
/Views
就像你所猜到的,我们推荐你把你的控制器类放在/Controllers 目录下, 把你的数据模型类放在/Models目录下, 然后把你的视图模板类放在/Views 目录下.
不过ASP.NET MVC framework 并不会让你总是使用这样的结构,而只是默认的项目模板使用了这种模式,而且我们更推荐它只是由于这种方式能够方便地结构化你的应用程序. 除非你拥有一个足够的理由,那你可以使用一种与此不同的文件布局,否则,我更推荐你使用这种模式.
将你的URL映射到你的控制器类
在大多数的web框架 (ASP, PHP, JSP, ASP.NET WebForms等等)中, 用来访问的URL一般典型地映射你物理存储器上的文件结构,比如说 "/Products.aspx" 或者 "/Products.php" 这样的URL 一般说明你的物理存储器上会有Products.aspx 或者 Products.php 这两个文件来处理这个request。当一个要求访问web应用程序的http request 到达web server的时候, web 框架会执行物理存储器上特定文件的代码,并且这个文件的代码就会全权负责处理这个request. 经常性地,此代码在Products.aspx 或者 Products.php文件内部使用了HTML markup 来帮助生成要返回给客户端的response.
而MVC framework却以另外一种方式来将URL映射到服务器上的代码. 它没有使用将URL映射到实际物理存储器上的文件这一模式,而是直接将URL映射到类. 这些类被称之为"Controllers"(控制器),他们才是接收到来的request,处理用户的输入和互动操作,执行适当的应用程序和在此基础上的数据逻辑的角色, 一个控制器类一般会调用一个单独的"视图"组件,而视图组件正是负责生成实际HTML输出,以响应原本的request.
ASP.NET MVC Framework 内置了一个非常强力的URL映射引擎,为你如何映射你的URL到控制器类提供了很大的弹性. 通过使用它,你可以非常方便地建立路由规则, 让ASP.NET在处理接收到的URL请求后能够找出一个特定的控制器去执行. 你还能够让路由引擎自动地转换你在URL中传递的变量.甚至ASP.NET会自动把他们当作声明好的参数传递到你的控制器中去. 我将会在这个系列未来的一篇blog里专门谈一下URL路由引擎的一些深入细节.
ASP.NET MVC中默认的URL到控制器类的路由
默认的ASP.NET MVC project会预先设置了一组URL名字转换规则,让你无需明确地设置任何东西,进而更容易地入门. 当你在Visual Studio 里创建的新的ASP.NET MVC project后,里面生成的Global.asax 文件中会自动声明一套默认的URL名字转换规则,这套规则是以名字为基础的,可以立即供你使用。
根据这套默认的名字转换规则,系统会将接受到http request当中的URL路径 (例如: /Products/)映射到一个名字遵循UrlPathController模式的类(例如: 默认的一个如 /Products/的URL会映射到一个名为ProductsController的类).
为了建立电子商务产品的浏览功能, 我们得往我们得工程里面加入一个名为"ProductsController" 的类 (你可以通过Visual Studio 菜单中的"Add New Item" 来方便地从模板中创建一个控制器类):
我们的ProductsController 类将会继承System.Web.MVC.Controller 这个基类. 当然从这个基类当中派生并不是必需的- 但是它包含了一套有用的辅助方法和功能能让我们在后面的工作中充分利用:
一旦我们在我们的project里面定义了这个 ProductsController 类, ASP.NET MVC framework 将会默认地使用它来处理所有的接收到的以"/Products/" 开头的URL. 这意味着它会自动调用这个控制器类来处理诸如"/Products/Categories", "/Products/List/Beverages", 以及 "/Products/Detail/3" 这些我们将会在我们电子商店应用程序中使用到的URL.
在一篇以后的blog 当中我们也会添加一个ShoppingCartController (来使得终端用户能够管理他们的购物车) 和一个 AccountController (使得终端用户能够在网站创建一个新的会员帐户并且登陆或者登出). 一旦我们把这些新的控制器类加入到我们的project中, 以 /ShoppingCart/ 和 /Account/ 开头的URL将会自动路由到他们来进行处理。
注意: ASP.NET MVC framework 并不要求你总是使用这套名字转换的模式. 我们的程序当中使用它的唯一理由只是它是一套在我们使用Visual Studio来创建ASP.NET MVC Project后就会默认加入到我们project中的映射规则. 如果你不喜欢这套规则,或者你想自定义一个不同的URL映射模式,你可以去ASP.NET Application Class (Global.asax)文件当中修改它. 我将会在以后的一篇blog里面告诉你怎么去做 (当我们向你展示一下URL映射引擎的cool点的时候).
理解控制器的Action方法
现在我们就在我们的project里面创建了一个ProductsController类,我们现在可以开始在应用程序当中添加逻辑来处理接收到的"/Products/" 这个URL了.
就像我们这篇blog开端说的那样,我们打算在这个电子商店网站中实现三种功能: 1) 浏览所有的产品种类, 2) 列出某个特定种类当中的所有产品, 还有3) 显示一个特定产品的详细信息. 我们将会使用以下的URL来代表这些功能:
URL格式 |
行为 |
URL 样例 |
/Products/Categories |
浏览所有的产品种类 |
/Products/Categories |
/Products/List/Category |
列出某个特定种类当中的所有产品 |
/Products/List/Beverages |
/Products/Detail/ProductID |
显示一个特定产品的详细信息 |
/Products/Detail/34 |
我们有两种方法,在我们的ProductsController类里面编码去处理这些接收到的URL需求. 一种方法是通过重写控制器基类里面的 "Execute" 方法然后手动的编写if/else/switching 逻辑结构来分析访问的URL需求然后选定相应的逻辑代码去处理它(类似于javaservlet的做法).
不过还有一种更加简单的方法就是使用MVC Framework内建的特性,我们可以在我们的控制器中定义"action方法" , 然后让控制器基类根据URL路由规则自动地执行相应的action方法.
举个例子, 我们可以在我们的ProductsController类当中添加以下三个控制器action方法(controller action methods)来处理我们上面说的电子商店功能:
在新的project建立的时候就已经预先设定好的URL路由规则将会把URL的子路径按顺序分割,并将分割后的各个字符串分别当作客户端要求访问的控制器名字和action名字来对待. 所以如果我们收到了一个/Products/Categories的URL Request, 路由规则将会把"Categories" 当作action的名字, 然后将会执行Categories()方法来处理这个request. 如果我们收到一个/Products/Detail/5的URL request , 路由规则将会把"Detail" 当作action的名字, 然后执行Detail()方法来处理这个request.
注意: ASP.NET MVC framework 不要求你总是使用这种action命名转换模式. 如果你想使用不同的URL映射模式 , 到ASP.NET Application Class (in Global.asax)文件里面去修改就可以了.
将URL参数映射到控制器的Action方法
有好几种方法可以在控制器类的action方法里面访问通过URL传递进来的参数.
控制器基类暴露了一组可供使用的Request和Response object. 这些object拥有和传统ASP.NET中你所熟悉的HttpRequest/HttpResponse object相同的API结构. 但是一个重要的不同就是现在这些object都是基于接口的而不是基于密封类的(特殊地: MVC framework 集成了新的System.Web.IHttpRequest 和 System.Web.IHttpResponse接口). 使用接口的好处在于非常方便地可以伪造他们- 这能够使控制器类的单元测试更加方便. 我将会在以后blog当中深入地介绍这些.
下面是一个我们在ProductsController类的Detail action方法中如何手动使用Request API去取得ID参数的例子:
ASP.NET MVC framework还支持自动地把URL传递进来的参数映射到action方法. 在默认情况下,如果你的action方法声明中存在参数, MVC framework 将会在接收到的request数据中查找是否有一个相应的HTTP request值拥有相同的名字. 如果有, 它会自动的将这个值作为参数传给相应的action方法.
例如, 我们可以重写Detail action方法来充分利用这种支持的好处,如下:
除了可以从request的querystring/form 集合中映射参数值外,ASP.NET MVC framework 还允许你改变MVC URL路由映射的内部结构在核心URL内部嵌入你的参数值(比如: 你可以使用/Products/Detail/3来取代/Products/Detail?id=3 ).
当你新建一个MVC项目时候默认的路由映射规则是以"/[controller]/[action]/[id]"格式声明的. 这意味着如果URL中按顺序除去控制器名字和action方法名字之后还有URL子路径的时候,这个子路径将会被默认的当作名为"id" 的参数对待- 这同样意味这它能够作为方法所声明的参数传入我们控制器的action方法.
这也意味着我们现在可以让我们自己的Detail 方法自动从URL取得ID参数 (e.g: /Products/Detail/3):
我可以在listaction方法中使用我所熟悉的方式,这样的话我就能够把种类的名字作为URL的一部分传递进来 (例如: /Products/List/Beverages). 出于让代码可读性更高的目的, 我将会在这个action中使用"category"来代替"id".
下面是ProductsController类的一个实现完整URL路由和参数映射支持的版本:
注意上面List action 是如何把category 参数作为URL的一部分, 然后将一个可选的页面索引作为querystring 的(我们在实现服务器端的分页并使用这个值来表示当前request要求显示哪一页的category数据).
我们MVC framework 中可选的参数将会在控制器action方法中使用nullable类型的参数声明. 因为我们List action方法中页面参数是nullable int类型的(这就是"int?"的含义), MVC framework将在URL当中存在这一参数的时候将它传进来,而不存在的时候就会传一个null进来. 你可以看看我另外一个blog去学习如何使用nullable类型(here).
建立我们的数据模型
我们现在已经拥有了一个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, Part3 和Part4.
我将右击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):
一旦我们定义了我们的LINQ to SQL 数据模型类, 我将会在我的Models 目录下添加一个新的NorthwindDataContext的部分类:
在这个部分类中我将会定义一些辅助方法来封装一些LINQ表达式,我们用这些表达式从数据库当中取得产品种类列表,某个特定种类当中的所有产品,以及一个特定产品的详细信息:
这些辅助方法能够使我们方便得取得ProductsController所需要的数据模型object (不用在控制器类内部写LINQ表达式了):
我们现在拥有了ProductsController类功能实现所需要的所有数据code/object.
完成我们的ProductsController类
在一个MVC架构为基础的应用程序中控制器负责处理接收到的request和用户的输入以及互动操作, 并在他们的基础上执行适当的程序逻辑(取得和更新存储在数据库里面的模型数据等等).
控制器一般不为一个request生成特定的HTMLresponse. 生成HTMLresponse的任务是由程序内部的“视图”组件所负责的- 它以与控制器分离的类/模板的方式实现. 视图组件试图将全部焦点集中在封装表现层逻辑上, 并且不包含任何程序逻辑以及从数据库中取得数据的代码(所有的程序逻辑应该在控制器中处理).
在一个典型的MVC web工作流当中, 控制器action方法将会处理接收到的web request, 使用传递进来的参数去执行适当的程序逻辑代码, 取得或者更新由数据库生成的数据模型object, 然后选择一个相应的视图将其作为UIresponse返回给客户端的浏览器. 作为返回视图整体工作的一部分, 控制器会显式地将生成视图response所需要的数据和变量传入视图类:
你可能会问- 像这样将控制器和视图分离的好处是什么? 为什么不将他们放在一个相同的类里面呢? 其主要动机是将你生成UI的代码和你的应用程序/数据逻辑分离.以更便于进行单元测试. 这还能使得你的应用程序更加容易维护- 因为这会使得你在视图模板里添加偶尔的应用程序/数据逻辑变得更加困难.
当实现我们ProductsController控制器内三个action方法的时候, 我们将会使用传入的URL参数来从数据库中取得所需的模型, 然后选择一个视图组件来发送一个相应的HTMLresponse. 我们将会使用控制器基类里面的RenderView() 方法来发送我们想使用的视图, 我们也可以显式地传递我们在发送视图响应时候所需要的数据.
下面是我们ProductsController的最终实现:
注意我们在以上action方法里面所使用代码行数是相当少的 (每个两行). 这部分是由于MVC framework 已经自动为我们处理了URL参数的转换逻辑 (把我们从需要写一大堆代码的困境中解救出来). 另外也是因为从业务的观点来看产品浏览机能也是相对比较简单的(action方法全以只读功能方式显示).
但是在一般情况下,你将会经常发现你的控制器类看起来比较单薄– 意味着控制器方法都是由相对简洁的action方法组成 (少于10行代码). 这通常是一个很好的迹象,能够使得你能够清晰地封装你的数据逻辑.
单元测试我们的ProductsController
你可能会很奇怪我们的下一步居然是测试我们的程序逻辑和功能。你可能会问:这怎么可能?我们还没有实现我们的视图呢,现在我们的应用程序根本不会生成哪怕半点的HTML Tag。好吧,我得告诉你这就是MVC框架吸引人的地方之一,我们可以在不依赖视图生成逻辑的情况下单元测试我们应用程序的控制器和模型逻辑. 就像你将会看到的那样我们甚至可以在创建我们的视图之前进行单元测试。
为了单元测试ProductsController类,我们得继续工作。当我们创建我们的ASP.NET MVC应用程序的时候Visual Studio曾经自动创建了一个Test Project ,现在我们将一个名为ProductsControllerTest的类加到那里面去。
然后我们定义一个简单的单元测试,让它来测试我们ProductsController 的Detail action方法:
ASP.NET MVC framework本身就被特别设计成有利于单元测试. Framework内部所有的核心API以及 contract 都是接口,提供了易于注入和自定义的可扩展点 (包括使用类似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 object), 你没有必要在实际的web-server 的环境当中运行单元测试. 取而代之的是你可以在一个普通的class library内部创建ProductsController 然后直接测试它. 这会显著的加速我们单元测试,同时简化了原本配置和执行所需的冗长过程.
如果我们使用Visual Studio 2008 IDE的话, 我们可以很容易地追踪我们测试执行的成败 (这个功能现在已经内建在 VS 2008 Professional中了):
我认为你将会发现ASP.NET MVC Framework使得单元测试的编写更加简单,并且对测试驱动的开发流程(TDD workflow)提供了很好的支持.
使用视图生成UI
我们完成了电子商店当中产品浏览功能的应用程序逻辑的实现和测试. 现在我们需要为它来实现HTML UI
我们将调用RenderView() 方法,这样的话就能在内部使用我们的ProductsController action方法所提供的视图相关的obeject,通过这个方法来实现负责生成正确UI的视图:
在以上这个代码的例子当中,RenderView 方法的第一个参数"Categories"表示我们将会调用的视图的名字, 第二个参数是我们想要传给视图类的种类数据列表,我们需要将它作为数据源去生成正确的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页面,通过使用master页面我们可以将我们网站中网页需要共享使用的页面布局外观封装起来. 我们将在我们工程的"Views"Shared目录下一个名为"Site.Master" 文件中完成我们的工作:
我们可以为我们的网站参照一个CSS的style sheet,这个sheet封装了所有的style, 然后使用master页面去定义网站页面整体的布局, 同时标识一些placeholder 区域,我们之后可以使用拥有特定内容的页面去填充它. 我们可以选择新的VS 2008 designer中一些很cool的特性去完成这项工作(包括HTML split-view designer, CSS Authoring, 和Nested Master Pages support).
理解/Views的目录结构
当你使用默认的Visual Studio 来创建ASP.NET MVC 工程的时候, 它会自动在"Views"目录下创建一个名为"Shared" 的子目录. 这是我们推荐的保存(在我们应用程序中需要在不同控制器之间共享的)Master 页面, 用户控件和视图的地方.
当为某一个单独的控制器建立视图的时候, ASP.NET MVC会默认的将其保存在"Views目录下的一些子目录当中. 默认情况下这个子目录的名字需要和控制器的名字相对应. 例如, 由于控制器类的名字叫做"ProductsController", 我们将会把其相关视图默认地保存在"Views"的Products 子目录下:
当我们在特定的控制器当中调用RenderView(string viewName) 方法的时候, MVC framework 将会自动地在"Views"ControllerName目录下面查找相应的 .aspx 或者 .ascx 视图模板,如果它无法找到合适的视图模板的话它就会去"Views"Shared 下面继续查找:
创建一个Categories视图
我们可以在Visual Studio 中我们project的Products目录上面点击右键,选择 "Add New Item" ,然后在菜单选项中选择"MVC View Page"这个模版,通过这个方式为我们的控制器类添加视图.这将创建一个新的.aspx页面,我们也可以将它关联到我们的Site.Master master页面上面去,以取得网站中页面的统一外貌和风格(就像通过master页面你可以取得完全的WYSIWYG设计器的支持):
当使用MVC的模式去新建应用程序的时候, 你需要让你的视图代码尽可能的简单, 并且确认视图代码纯粹只是和生成UI有关. 应用程序和数据取得的逻辑应该在控制器类中编写. 在调用RenderView方法的时候,控制器类能够选择将生成视图所需要的数据object传给视图类. 比方说,以下在ProductsController类的 Categories action 方法中我们将一组种类数据集合列表传递给了Categories视图:
MVC视图页面默认继承自System.Web.Mvc.ViewPage这个基类, 其提供了许多MVC特定的辅助方法与属性让我们可以使用来构建我们的UI. 这些视图页面的属性之一就是ViewData, 它提供了一些特定视图数据的访问服务,而这些特定的视图数据正是控制器类作为参数传递到RenderView() 方法中的.
在你的视图中你可以选择两种方式来访问这些视图数据,一种是晚约束方式(late-bound),一种是强类型方式(strongly typed ). 如果你的视图是继承自ViewPage的话, 那么视图数据属性会被定义晚约束的数据字典(late-bound dictionary).而如果你的视图是继承自泛型基类ViewPage<T>的话 - T表示控制器传给视图的数据object的类型 - 那么视图数据属性将会对其进行强制匹配,使其和传入数据的类型保持一致,这就是强类型方式.
比如所, 我们的Categories 视图后台的类如下所示继承自 ViewPage<T> - 这里T是一组种类object的列表:
这意味着当实际编码的时候我在我的视图代码内能够得到完全的类型安全,智能化以及编译时的检测:
生成我们的Categories视图:
如果你能记得在这篇blog开端时候的那些屏幕截图的话,我们需要在我们的Categories视图当中显示一组产品种类列表:
我在Categories视图实现工作中可以选择使用两种HTML UI生成代码方法中的一种: 1) 在.aspx 文件内使用内联代码, 或者 2) 在.aspx 文件内使用ASP.NET的服务器控件然后在我的后台代码中进行数据绑定.
生成方法一:使用内联代码
ASP.NET页面, 用户控件以及Master页面现在都支持使用<% %>和<%= %>的标识来将代码嵌入到HTML markup当中. 我们可以在我们的 Categories 视图中使用这个技术来方便地放一个foreach的循环来生成目标HTML种类列表:
VS 2008 在代码编辑器当中对于VB和C#提供了完全的代码智能感知支持. 这意味着我们能够对传到我们视图的Category 模型的object数据进行智能化输入:
VS 2008 也提供对于内联代码的完全调试支持(允许我们设置断点以及动态地改变视图当中的任何东西):
生成方法二: 使用服务器控件
ASP.NET页面, 用户控件以及Master页面现在都支持使用声明好的服务器控件去封装HTML UI的生成. 不使用如上上面所说的内联代码的话, 我们可以使用.NET 3.5当中新的<asp:listview>控件 来生成我们目标列表的UI:
注意上面ListView控件是如何封装生成一组值以及处理列表当中没有任何项目的情况的(<EmptyDataTemplate>使得我们不用在markup当中去写if/else语句了). 我们能够使用如下的后台代码将一组category类的object数据绑定到listview控件:
重要: 在MVC的世界当中我们在视图后台代码类中只需要放入生成逻辑(不用放任何的应用程序逻辑和数据逻辑). 注意在上面我们仅仅放入了为 ListView控件指定强类型的ViewData的代码逻辑. 我们的ProductsController 控制器类才是我们从数据库取得种类数据列表的地方- 不是视图.
这个在视图模版中使用ListView服务器控件的版本同我们前一个使用内联代码的版本一样都能生成HTML. 我们在页面上没有一个<form runat="server"> 的控件, 没有视图状态( viewstate), ID 值或者其他markup. 所以我们拥有的是友好的纯粹的CSS HTML:
Html.ActionLink 方法
一件你必须注意到的事情是不管在内联代码实现的版本里面还是服务器控件实现的版本里面都有着调用Html.ActionLink 方法的代码片断:
这个Html object是 ViewPage 基类上的辅助属性, 而ActionLink 方法是这个object的一个辅助方法,它的作用是动态的生成可以链接回控制器action方法的HTML超链接. 如果你看到了以上的HTML输出图片, 你就可以看到通过这个方法产生的实际HTML输出的例子:
我正在使用的Html.ActionLink辅助方法的声明如下:
string ActionLink(string text, object values);
其第一个参数表示了产生的超链接的内部文本内容 (例如: <a>text goes here</a>). 第二个参数表示了一个匿名的object,其实就是一组按顺序排列好的值的集合,我们可以使用它去生成实际的URL (你可以将这认为是一个产生数据词典的更清晰方法). 在以后的blog中我会就URL路由引擎是如何工作的介绍更多的细节. 然而作为一个概要,我能告诉你的是,你不仅可以使用URL路由系统去处理接收到的URL访问要求,而且可以利用它去生成你想要对外输出的HTML. 如果你有类似如下的路由规则:
/<controller>/<action>/<category>
然后在ProductController的Category 视图里面写下了如下代码:
<%= Html.ActionLink("Click Me to See Beverages", new { action="List", category="Beverages" } %>
ActionLink 方法会使用你应用程序当中的URL映射规则去将你的参数值自动转换成你生成的输出:
这可以让你的应用程序内部更容易地生成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