ASP.NET MVC框架(第一部分)
【原文地址】ASP.NET MVC Framework (Part 1)
【原文发表日期】 Tuesday, November 13, 2007 3:45 AM
两个星期前, 我在博客里讨论了ASP.NET的一个新MVC(模型、视图,控制器)框架,我们将在不久的将来作为一个可选功能来支持。该框架提供了一个结构化的模型,来加强应用中的清晰关注分离,方便你单元测试代码和支持TDD流程。它还提供了对你在应用中发布的URL的更多的控制,也可以对从中输出的HTML提供更多的控制。
之后,我回答了来自迫切想了解更多详情的很多人的很多问题。鉴于如此高的兴趣,我觉得,写几个贴子更详细地描述如何使用这个框架,也许更有意义些。这是我将在以后几个星期里要撰写的相关贴子的第一个。
一个简单的电子商务店面应用
我将使用一个简单的电子商务商店应用来示范ASP.NET MVC框架的工作原理。在今天的贴子里,我将实现一个产品列单,以及相关的浏览应用场景。
具体来说,我们将建造一个网上商店,允许用户在访问该网站上的/Products/Categories网址时 浏览产品分类列表:
当用户点击上面网页上的产品分类链接时,他们将转到一个产品分类列表URL /Products/List/CategoryName上,该页面列出了指定分类中的还在销售的产品:
当用户点击个别的产品时,他们将转到产品细节URL /Products/Detail/ProductID上,这个网页将显示用户选定的产品的更多细节:
我们将使用新的ASP.NET MVC框架来实现上述的所有功能。这将会允许我们在应用的不同组件间保持“清晰的关注分离”,允许我们更轻易地集成单元测试和测试驱动的开发。
创建一个新的ASP.NET MVC应用
ASP.NET MVC框架包含一个Visual Studio项目模板,方便你创建新的MVC web应用。选择文件->新项目菜单,选择“ASP.NET MVC Web 应用”模板,用它创建一个新web应用。
在默认情形下,当你使用该选项生成一个新应用时,Visual Studio 将为你创建一个新的解决方案,然后往里面加2个项目。第一个项目是web项目,在其中你实现你的web应用的功能。第二个项目是个测试项目,你可以在其中编写单元测试,来测试你的应用代码:
你可以在ASP.NET MVC 框架中使用任何单元测试框架,包括NUnit, MBUnit, MSTest, XUnit以及其他的框架。VS 2008专业版现在包含了对MSTest的内置测试项目的支持(VS 2005版本的MSTest要求你拥有Visual Studio Team System版本才能使用),当你使用VS 2008时,默认的ASP.NET MVC 项目模块自动生成这样的测试项目。
我们还将发布可用以NUnit, MBUnit 和其他单元测试框架的项目模板,所以,如果你更喜欢那些框架的话,你可以轻松地一次点击即生成你的应用和可以马上使用的相应的测试项目。
理解项目的目录结构
ASP.NET MVC 应用的默认目录结构有三个顶层目录:
- /Controllers
- /Models
- /Views
你大概可以猜出来,我们建议把控制器类置于 /Controllers 目录之中,你的数据模型类置于/Models目录之中,你的视图模板置于 /Views 目录之中。
虽然ASP.NET MVC框架并不强迫你总是使用这个结构,但默认的项目模板使用这个模式,我们也把它作为结构化应用的一种比较容易的方式向你推荐。除非你有好的理由使用另外的文件布局,我建议你使用这个默认模式。
把URL映射到Controller类
在大多数web框架(ASP, PHP, JSP, ASP.NET WebForms等等)里,到来的URL一般都映射到保存在硬盘上的模板文件。譬如,"/Products.aspx"或者" /Products.php" URL一般都在硬盘上有个对应的Products.aspx 或Products.php 模板文件来处理请求。当一个web应用的http请求进入web服务器时,web框架运行由硬盘上的模板文件指定的代码,然后这代码负责处理该请求。很多 时候,这代码使用Products.aspx 或 Products.php文件中的HTML 标识来帮助生成返回客户端的响应。
MVC框架一般以不同的方式把URL映射到服务器代码上。它不是将URL映射到硬盘上的模板文件,而是直接把URL映射到代码类上。这些类称为 “Controllers(控制器)”,它们负责处理到来的请求,处理用户输入和交互,执行基于输入和交互的相应的应用和数据逻辑。然后,一个 Controller类一般会调用单独的“视图”组件,该组件负责生成请求的实际的HTML输出。
ASP.NET MVC框架包括一个非常强大的URL映射引擎,在如何把URL映射到Controller类方面,该引擎提供了很多灵活性。你可以使用它来轻松地设置 routing(路径选择,路由)规则,然后ASP.NET会根据这些规则,对进来的URL进行评估,选出一个Controller来运行。然后你也可以 让routing引擎自动分析出你在URL里定义的变量,让ASP.NET自动把这些变量作为参数传给你的Controller。我将在这个系列将来的一 个贴子里,讨论涉及URL routing引擎的比较高级的场景。
映射到控制器类的默认ASP.NET MVC URL Routing规则
在默认情形下,ASP.NET MVC项目有一套预先配置好的URL routing规则,这些规则允许你不用配置什么,就可以轻松地上路。这样,使用一套默认的基于名称的URL映射约定,你就可以开始编写代码了,这些约定 是在Global.asax文件(由Visual Studio中新的ASP.NET MVC项目模板生成的)中的ASP.NET Application类中声明的。
默认的命名约定是这样的:把进来的HTTP请求的URL路径的开头部分,譬如 /Products/,映射到一个类,该类的名称遵循UrlPathController的模式,譬如在默认情形下,一个以/Products/开头的URL 会被映射到名为ProductsController的类上。
为建造我们的电子商务产品浏览功能,我们将在我们的项目中加一个新的“ProductsController”类 (你可以使用Visual Studio中的“添加新项”菜单从模板中轻松地创建一个Controller类):
我们的ProductsController是从System.Web.MVC.Controller 基类继承而来,从这个基类继承而来并不是必需的,但它含有一些我们以后可以使用的非常有用的辅助方法和功能:
在项目中定义这个ProductsController类之后,在默认情形下,ASP.NET MVC 框架就会使用它来处理所有到来的以"/Products/"开头的URL的应用请求。这意味着,它会自动被调用来处理我们将在我们的网上商店应用中开启 的"/Products/Categories", "/Products/List/Beverages", 和 "/Products/Detail/3" 等URL。
在将来的贴子里,我们还将添加一个ShoppingCartController(以允许用户管理他们的购物车)以及 AccountController (允许用户在网站上创建新的成员帐号,实现登录和退出等功能)。在向我们的项目里添加这2个新的控制器类之后,以/ShoppingCart/ 和 /Account/开头的URL就会自动地导向到这些类做处理。
注:ASP.NET MVC框架并不要求你总是使用这个命名约定模式。我们的应用默认使用这个模式的唯一原因是因为在我们使用Visual Studio创建新的ASP.NET MVC项目时,有一个配置了这个模式的映射规则被自动地添加到了我们的ASP.NET Application 类中。如果你不喜欢这个规则,或者想使用另外的URL映射模式来对它进行定制,那么就到Global.asax中的ASP.NET Application类中做改动。我会在以后的一个贴子讨论该怎么做,到时我也会展示一些URL routing引擎允许的一些非常酷的场景。
理解控制器的Action方法
既然我们已经在项目里创建了一个ProductsController类,我们可以开始添加逻辑来处理来到应用的"/Products/" URL了。
在本贴子的前面定义我们的电子商务店面用例时,我说过我们将在网站上实现3个场景:1) 浏览所有的产品分类, 2) 列出特定分类里的产品, 和 3) 显示特定产品的细节。我们将使用下列这些SEO友好的URL来处理这三个场景:
URL格式 | 行为 | URL例子 |
/Products/Categories | 浏览所有的产品分类 | /Products/Categories |
/Products/List/Category | 列出特定分类里的产品 | /Products/List/Beverages |
/Products/Detail/ProductID | 显示特定产品的细节 | /Products/Detail/34 |
我们可以用几种方式在ProductsController类中编写代码来处理这三类到来的URL。一种方式是,覆盖Controller基类中的 “Execute”方法,手工编写我们自己的 if/else/切换逻辑,对照用户请求的URL,然后执行适当的逻辑来处理这个请求。
但一种容易得多的方式是,使用MVC框架中内置的功能,该功能允许我们在我们的控制器中定义“action方法”,然后由Controller基类根据我们应用使用的URL routing规则来自动调用适当的action方法来执行。
譬如,我们可以往我们的ProductsController类里添加如下所示的三个控制器action方法,来处理上述的三个电子商务URL场景:
在项目创建时默认配置的URL routing规则会把紧随控制器名称之后的子路径当作请求的action名称来对待。所以,如果我们收到一个/Products/Categories 的URL请求,routing规则会把“Categories”作为一个action的名称来对待,Categories() 方法就会被调用来处理这个请求。如果我们收到的是 /Products/Detail/5 URL请求,routing规则就会把“Detail"”当中action的名称,Detail() 方法就会被调用来处理请求,等等。
注:ASP.NET MVC框架并不要求你总是使用这个action命名约定模式。如果你想使用不同的URL映射模式,到 Global.asax文件中的ASP.NET Application类去做改动。
把URL参数映射到Controller的Action方法上
在Controller类中的action方法中可以用几个方法访问URL中的参数值。
Controller基类呈现了可以使用的Request 和Response对象。这些对象跟ASP.NET中你已经熟悉的HttpRequest/HttpResponse对象拥有完全一样的API结构。一个 非常重要的区别是,这些对象现在是基于接口(interface)的,而不是封闭的类。具体来说,MVC 框架将发布System.Web.IHttpRequest和System.Web.IHttpResponse接口。这些对象是基于的接口的好处是,现 在非常容易mock(模仿)它们,这样,就可以方便对控制器类的单元测试。在将来的博客贴子里,我将对此进行深入的讨论。
下面是一个如何在ProductsController类的Detail action方法中使用Request API来手工获取ID查询字符串值的例子:
ASP.NET MVC 框架还支持自动将进来的URL的参数值映射成action方法的参数。在默认情形下,如果你的action方法有个参数的话,MVC框架会检查进来的请求 的数据,看是否有个同样名称的对应的HTTP请求值。如果有的话,它会自动将其作为参数传入你的action方法。
譬如,我们可以利用这个支持来重写我们的Detail action方法来,将其简化,象下面这样:
除了从请求的查询字符串/表单集合中映射参数值外,ASP.NET MVC框架还允许你使用MVC URL route映射基础设施在核心URL本身内嵌参数值(譬如,不是使用/Products/Detail?id=3,而是使用/Products /Detail/3 )。
当你创建一个新的MVC项目时,声明的默认的路径映射规则拥有这样的格式,“/[controller]/[action]/[id]”。这意味 着,如果URL中在控制器名称和action名称之后还有任何子路径的话,在默认情形下,它将作为一个名为“id”的参数处理,会自动地作为一个方法参数 传给我们的控制器action方法。
这意味着我们现在可以使用我们的Detail 方法来处理从URL路径(譬如/Products/Detail/3)中获取ID参数:
我可以对List action使用类似的方法,这样我们可以将分类名称作为URL的一部分传进来(譬如:/Products/List/Beverages)。为了使得代 码容易阅读,我对routing规则做了一个小变动,这样不是把参数名定为“id”,对这个action,它被称作“category”。
下面是实现了完整URL routing和参数映射支持的ProductsController类的一个版本:
注意上面List action方法接受一个作为URL一部分的category参数,然后一个作为URL查询字符串一部分的可缺省的网页索引参数(我们将实现服务器端分页,将使用该参数值来表示我们应该显示对应分类数据的哪一页)。
我们的MVC 框架中的可缺省的参数是通过Controller Action方法上nullable的类型参数来处理的。因为我们List action的分页参数是个nullable的int,从语法上来说,即是int? ,如果这个参数存在于URL中,MVC框架会将其值传给对应方法,如果不存在,会传入null。参阅我以前的关于?? null coalescing operator帖子以了解如何操作象这样传入的nullable类型参数的一个有用的技巧/诀窍。
建造数据模型对象
至此,我们有了一个 ProductsController类,内含三个action方法,准备好处理进来的web请求了。下一步将是建造一些类,来帮我们操作数据库,从中获取处理这些请求所需的适当的数据。
在MVC世界里,“model(模型)”是负责保持状态的应用组件。在web应用中,这个状态一般都持久于数据库之中(譬如,我们也许有一个Product 对象,用来代表我们SQL数据库里Products表中的产品数据)。
ASP.NET MVC框架允许你为获取和管理你的模型,可以使用你想要的任何数据访问模式或框架。如果你要使用ADO.NET DataSets/DataReaders (或者建于它们之上的抽象),你可以那么做。如果你更喜欢使用象NHibernate, LLBLGen, WilsonORMapper, LINQ to SQL/LINQ to Entities这样的对象关系映射器(ORM),你也绝对可以那么做。
对我们的电子商务例程,我想用随 .NET 3.5 和 VS 2008发布的内置LINQ to SQL ORM 。你可以从我的还在撰写中的讨论LINQ to SQL 的博客系列中了解其中详情,特别是一定要阅读一下其中的第一部分,第二部分,第三部分,和第四部分的帖子。
从右击VS中的MVC web项目的“Models”子目录开始,选择“添加新项”,加一个 LINQ to SQL 模型。在LINQ to SQL ORM 设计器中,我将定义三个数据模型类,分别映射到SQL Server Northwind数据库中的Categories, Products, 和Suppliers 表(阅读我的LINQ to SQL 系列的第二部分学习该怎么做):
定义完LINQ to SQL 数据模型类之后,然后我还将添加一个新NorthwindDataContext部分类到我们的Models目录中:
在这个类中,我将定义几个辅助方法封装一些LINQ表达式,这些表达式是用来从数据库中获取独特的Category对象,获取指定分类的所有Product 对象,以及基于指定的ProductID获取单独的 Product对象:
这些辅助方法将方便我们在ProductsController类中干净利索地获取所需的数据模型对象 (而不用在Controller类中编写LINQ表达式):
至此,我们就有了为完成我们的ProductsController功能所需的所有的数据代码和对象。
完成ProductsController类的实现
基于MVC的应用中的控制器类负责处理到来的请求,处理用户输入和交互,并且基于这些输入和交互执行适当的应用逻辑(获取和更新储存在数据库中的模型数据等等)。
控制器一般不对请求生成特定的HTML响应。生成HTML响应的任务是为应用中的“视图”组件所拥有,这些视图是通过独立于控制器的 单独的类或模板实现的。视图的目的是完全注重于封装表现层的逻辑,不应该包含任何应用逻辑或数据库数据获取的代码的(所有的应用逻辑应当为 Controller来处理)。
在一个典型的MVC web流程中,控制器action方法负责处理进来的web请求,使用传入的参数值执行适当的应用逻辑代码,从数据库中获取或更新数据模型对象,然后选择 使用一个“视图”来显示返回给浏览器的界面响应。作为选择适当的视图来显示的一部分,控制器会明确地以参数的形式向“视图”传入视图所需的所有的数据和变 量,以使后者显示适当的响应:
你也许在想,象这样分开Controller和View有什么好处呢?为什么不把它们放在同一个类里呢?象这样分割应用的主要动机在于帮助你加强应 用/数据逻辑与你的界面生成代码间的分离。这可以在隔离你的界面显示逻辑的情形下,极大地方便你单元测试你的应用/数据逻辑。它还有助于使得你的应用更好 维护,因为它妨碍了你无意中把应用/数据逻辑加到你的视图模板里的可能。
在实现我们ProductsController类的三个控制器action方法时,我们将根据进来的URL参数值从数据库中获取适当的模型对象, 然后选择一个“视图”组件来显示适当的HTML响应。我们将使用Controller基类的一个RenderView() 方法来指定我们想要使用的视图,以及明确地把我们要视图在显示响应时使用的特定数据传入该方法。
下面是我们的ProductsController实现的最终结果:
注意,我们的action方法的代码行数目很小(每个方法只有2行),部分原因是因为URL参数分析逻辑完全是由MVC框架为我们做的(给我们省了很多行代码),还有部分原因是因为产品浏览场景从业务逻辑的角度来说相当简单(涉及的action方法都是只读的显示场景)。
但总的来说,你经常会发现你有的都是些有时被称为“瘦控制器”的东西,即控制器方法充满了相当简短的action方法(少于10行代码)。这经常是好的迹象,表明你非常干净地封装了你的数据逻辑,也非常好地分隔了你的控制器逻辑。
单元测试ProductsController
你也许会感到惊奇,我们要做的下一步居然是测试我们的应用逻辑和功能。你也许会问,这怎么可能呢?我们还没有实现我们的视图呢,我们的应用目前并不 显示一个HTML tag。其实呢,使得MVC方法有魅力的部分原因就是我们可以完全独立于视图/Html生成逻辑来测试Controller和Model 逻辑。在下面你将看到,我们甚至可以在创建视图前单元测试这些对象。
为单元测试我们在编写的ProductsController类,我们将往测试项目里加一个ProductsControllerTest类,这个测试项目是在我们使用Visual Studio创建我们的ASP.NET MVC应用时,默认添加到我们的解决方案里的:
然后我们将定义一个简单的单元测试,测试我们的ProductsController的 Detail action方法:
ASP.NET MVC 框架是特地设计来促成轻松的单元测试的。框架中的所有的核心API和契约都是接口,提供了大量的扩展点以促成轻松的对象注入和定制(包括使用象 Windsor, StructureMap, Spring.NET, 和ObjectBuilder这样的IOC容器的能力)。开发人员将能够使用内置的mock类,或者使用任何.NET 类型mock框架来模拟他们自己的MVC相关对象的测试版本。
在上面的单元测试中,你可以看到一个例子,我们是如何在调用 Detail() action 方法之前,往我们的ProductsController里注入了一个伪(dummy)“ViewFactory”实现的。这么做的话,我们就覆盖了默认 的ViewFactory,否则的话,默认的ViewFactory会创建和显示我们的视图。我们可以使用这个测试ViewFactory实现来做隔离, 只对我们ProductController的Detail action的行为进行测试(而不必调用实际的视图来做测试)。注意我们是如何在Detail() action方法被调用之后,使用了三个 Assert 语句来核实该方法的正确的行为确实发生了(具体地说,该方法获取了正确的Product对象,然后将它传给了适当的视图)。
因为我们可以mock和模拟MVC框架中的任何对象(包括 IHttpRequest 和 IHttpResponse 对象),你不用再在web服务的环境里运行单元测试,我们可以在常规的类库里创建我们的ProductsController对象,然后对它直接测试。这 可以极大地加快单元测试的运行速度,以及简化对它们的配置和运行。
如果我们使用 Visual Studio 2008 IDE,我们还可以轻易地跟踪我们运行测试的结果(这个功能现在已经成为VS 2008 专业版的一部分):
我想你会发现ASP.NET MVC 框架极大地方便了测试的编写,而且促成了非常好的TDD流程。
使用视图显示界面
我们完成了我们电子商务应用的产品浏览部分的应用+数据逻辑的实现和测试,现在我们需要实现相关的HTML界面。
我们将通过实现“视图”来实现,这些视图将使用我们ProductsController的action方法在调用RenderView() 方法时提供的跟视图有关的数据对象,来显示适当的界面:
在上面的代码例子里,RenderView方法的“Categories”参数表示我们要显示的视图名称,第二个参数是我们要传给视图对象并要视图对象据此显示适当HTML界面的分类对象的列表。
ASP.NET MVC框架支持使用任何模板引擎(包括象NVelocity, Brail,以及你自己想要编写的任何模板引擎)来帮助生成界面。在默认情形下, ASP.NET MVC 框架使用ASP.NET中现有的ASP.NET 页面 (.aspx), 母版页 (.master), 和用户控件 (.ascx) 。
我们将使用内置的ASP.NET 视图引擎来实现我们的电子商务应用的界面。
定义Site.Master文件
因为我们将要在网站上建造很多页面,我们先来定义一个母版页,用以封装整个网站公用的HTML布局/样式。我们将在我们项目的"Views"Shared 目录里创建一个名为“Site.Master”的文件:
我们可以引用一个外部的CSS样式文件来封装整个网站的所有样式,然后使用母版页来定义网站总的布局,以及指定我们要具体页面填充相关内容的内容placeholder 区域。在做的时候,我们也可以使用VS 2008 中的新设计器的所有的酷功能,包括HTML分割视图设计器,编著CSS和嵌套母版页支持等。
理解/Views目录结构
在默认情形下,当你使用Visual Studio创建新的ASP.NET MVC 项目时,它会在“Views”根目录下生成一个“Shared”子目录。这是存放应用中为多个控制器所共享的母版页,用户控件和视图的推荐使用的地点。
在建造为特定个别控制器所用的视图时,默认的 ASP.NET MVC 约定是,把它们存放在"Views 根目录的子目录里。在默认情形下,子目录的名字应该对应于控制器的名字。譬如,因为我们正编写的Controller类叫 “ProductsController”,在默认情形下,我们将在"Views"Products 子目录里存放跟它相关的特定视图:
当我们在一个特定的Controller中调用 RenderView(string viewName)方法时,MVC框架会自动地首先在"Views"ControllerName 目录里寻找对应的.aspx 或 .ascx视图模板,如果它找不到适当的视图模板,然后它会在 "Views"Shared目录寻找。
创建一个Categories视图
我们可以在 Visual Studio 中 Products 目录上使用“添加新项”菜单选项,然后选择“MVC视图网页”项模板,为我们的ProductsController 创建一个“Categories”视图。这会生成一个新的.aspx 页面,我们可以将它跟我们的 Site.Master母版页相关联,来得到总的外观(就象母版页一样,你会得到即见即所得设计器的支持):
在使用MVC模式建造应用时,你要把你的视图代码尽可能地保持简洁,确认视图代码纯粹是用来显示界面。应用和数据获取逻辑应该只在 Controller类里编写。然后Controller类就可以在调用RenderView 方法时选择把所需的数据对象传递给视图。譬如,在下面,我们的ProductsController类的 Categories action方法中,我们把 一个Category对象的List集合传给了Categories视图:
MVC视图页默认是从System.Web.Mvc.ViewPage 基类继承而来的,该基类提供了可为我们构建界面时所用的许多特定于MVC的辅助方法和属性。ViewPage的其中一个属性名叫“ViewData”,通 过它,你可以访问Controller作为参数传给 RenderView()方法的特定于视图的数据对象。
从你的视图里,你可以后期绑定或以强类型的方式访问“ViewData”。如果你的视图是从ViewPage继承而来,那么ViewData属性是 个后期绑定的字典。如果你的视图是从基于泛型的ViewPage<T>继承而来,其中T表示Controller传给视图的ViewData 的数据对象的类型,那么ViewData属性就是强类型的,匹配你的Controller传入的数据的类型。
譬如,如下所示的我的Categories视图的后台类是从ViewPage<T>继承而来,我指明T为Category对象的一个List
这意味着在我的视图代码里操作ProductsController.Categories() 提供的List<Category> ViewData时,我将得到完整的类型安全, intellisense和编译时检查:
显示Categories视图:
如果你还记得本帖子最前面的截图的话,我们要在我们的 Categories 视图里显示产品分类列表:
我可以在我的Categories视图实现里用2种方式编写这个HTML界面生成代码:1) 在.aspx 文件里使用行内代码, 或者 2) 在.aspx 文件中使用服务器控件,然后在后台代码里使用数据绑定。
显示方法1:使用行内代码
目前的ASP.NET网页, 用户控件和母版页支持使用 <% %> 和 <%= %>的句法来在html 标识内嵌入显示代码。我们可以在Categories 视图里使用这个技巧,轻松地编写一个foreach循环,来生成HTML分类列表:
VS 2008在源码编辑器内为VB和C#提供完整的代码intellisense。这意味着,在对传入视图的Category模型对象操作时,我们将得到intellisense:
VS 2008还为行内代码提供了完整的调试器支持(允许我们在调试器对视图中的代码设置断点以及动态检查任何东西):
显示方法2:使用服务器端控件
ASP.NET网页,用户控件和母版页还提供对使用声明式服务器端控件封装HTML界面生成的支持。不是象上面那样使用行内代码,我们可以使用 .NET 3.5中新的<asp:listview> 控件来生成列表界面:
注意上面 ListView 控件封装了显示值列表的情形,还负责处理列表中没有任何东西的情形(<EmptyDataTemplate>省略了需要在标识中编写 if/else 语句的麻烦)。然后我们可以象下面这样,在后台代码里,将我们的分类对象绑定到listview控件上:
重要注意事项:在MVC世界里,我们只想要把显示代码放在我们视图的后台代码里,不包括任何应用或数据逻辑。注意上面我们只有把强类型的 Category对象的ViewData集合赋值给ListView控件的逻辑。我们的ProductsController控制器类才是实际从数据库获 取Category对象列表的责任者,不是视图。
我们视图模板的ListView服务器端控件版本然后就会生成跟上面行内代码版本完全一样的HTML。因为我们在页面里没有 <form runat="server">控件,ViewState,ID值以及其他的标识都不会生成,只有纯粹的CSS友好的HTML:
Html.ActionLink方法
你也许注意到的一件事情是,在上面视图代码片断中行内代码以及服务器端控件两个版本中对一个Html.ActionLink 方法的调用:
Html对象是 ViewPage 基类的一个辅助属性,ActionLink方法是它的一个辅助方法,它方便你动态地生成连回到控制器的action 方法的HTML超链接。如果你看一下上面生成的HTML输出图,你可以看到一些由该方法生成的一些HTML输出例子:
<a href="http://weblogs.asp.net/Products/List/Beverages">Beverages</a>
我使用的Html.ActionLink方法的签名是这样的:
string ActionLink(string text, object values);
第一个参数表示要显示的超链接的内容(譬如<a>这里是文字</a>),第二个参数是个匿名对象 ,它代表用以生成实际URL的一串值,你可以认为它是生成字典的一个比较干净的方式。我会在将来讨论URL routing引擎的博客帖子里仔细讨论这个参数的运用情形。但简而言之,你可以使用URL routing系统既处理进来的URL,也可以用它来生成你可以在返回的HTML输出的URL。如果我们的routing规则是象这样的:
/<controller>/<action>/<category>
那么在ProductController的Category视图里编写这样的代码时:
<%= Html.ActionLink("Click Me to See Beverages", new { action="List", category="Beverages" } %>
ActionLink方法就会使用你应用的URL映射规则,换进你的参数,生成这样的输出:
<a href="http://weblogs.asp.net/Products/List/Beverages">Click Me to See Beverages</a>
这方便了在你应用中生成URL和到你的控制器的AJAX回调。它也意味着你可以在一个地方更新你的URL routing规则,你整个应用中的代码会在对进来的URL的处理和外出的URL的生成过程中自动采用新的变化。
重要注意事项: 为加强可测试性,目前的MVC框架并不支持你视图中针对服务器端控件的postback事件,取而代之的是,ASP.NET MVC应用生成超链接和对控制器action的AJAX回调,然后只使用视图(以及其中的任何服务器端控件)显示输出。这有助于确保你的视图逻辑保持在最 小限度,只注重于显示,以及你可以单元测试你的Controller类,独立于你的视图,核实所有的应用和数据逻辑行为。我在将来的帖子里会对此做更深入 的讨论。
结语
这第一个贴子非常长,但希望它对新的ASP.NET MVC框架中所有不同的组件是如何组合在一起的,如何使用它打造常见的现实世界场景的应用提供了一个相当广泛的综览。 ASP.NET MVC的第一个公开预览版将在几个星期内发布,你将能够使用它来做我上面描述的一切。
虽然很多MVC固有的概念(特别是关注分离的观念)对该贴子的很多读者来说是比较新的,但希望本贴子展示了我们正在开发的ASP.NET MVC实现是如何很干净地嵌合到现有的ASP.NET, .NET, 和 Visual Studio框架中的。你可以使用.ASPX, .ASCX 和 .MASTER文件以及ASP.NET AJAX创建你的ASP.NET MVC 视图。今天ASP.NET中的非界面功能,譬如表单认证, Windows认证, 成员,角色, Url授权, 缓存, Session 状态, 用户信息,健康监测, 配置,编译,本地化以及 HttpModules/HttpHandlers 都是完全支持MVC模型的。
如果你不喜欢MVC模型,或者发现它对你的开发风格来说并不自然的话,你完全不必强用它的。它完全只是提供选项,并不替代现有的 WebForms Page Controller 模型。WebForms和MVC这2个模型在以后都会得到完全支持和改进。如果你想的话,你甚至可以建造一个应用,部分使用WebForms编写,部分使用MVC编写。
如果你喜欢上面贴子里的东西的话(或者感兴趣想进一步了解的话),留意一下我这段时间的博客。我将进一步讨论MVC概念,使用它们来进一步建造我们的电子商务应用,展示更多的MVC特性。
希望本文对你有所帮助,
Scott