【原文地址】ASP.NET MVC Framework
【原文发表日期】 Sunday, October 14, 2007 10:41 PM
过去的几年里,很多人要求ASP.NET的一件事情就是对使用基于model-view-controller(模型-视图-控制器,简称MVC)架构来开发web应用的内置支持。
上个周末在Austin举行的Alt.NET大会上,我首次对我的团队正在开发的新ASP.NET MVC 框架作了一个公开的演示。你可以在Scott Hanselman这里的博客上观看我的讲座的录像。
我们将在今年稍后发布该框架的一个公开预览版,然后在明年的上半年将它作为完全支持的ASP.NET特性推出。
模型-视图-控制器(MVC)框架是什么东西?
MVC是个将一个应用的实现分成三个组件角色的框架技术:模型,视图和控制器。
- 在基于MVC的应用里,Model(模型)是负责保持状态的应用组件。这个状态通常都持久于数据库之中(譬如,我们也许会有一个Product(产品)类用来代表SQL中的Products数据表中的订单数据)。
- 在基于MVC的应用里,View(视图)是负责显示用户界面的组件。这个UI通常是使用模型数据来创建的(譬如,我们也许会生成一个Product"编辑"视图,根据当前Product对象的状态,显示文本框,下拉框和复选框等)。
- 在基于MVC的应用里,Controller(控制器)是处理用户交互,操作模型和最终选择用哪个视图来显示UI的组件。在MVC应用中,视图只是用来显示信息而已,是控制器来处理和回应用户的输入和交互的。
使用MVC方法的一个好处是,它有助于促进应用中模型,视图,控制器间的关注的清晰分离。保持关注的清晰分离使得对应用的测试极其容易,因为不同应用组件间的契约的定义和表达是更明确的。
MVC模式也有利于促进红/绿式测试驱动的开发 (TDD),通过它,你可以在你实际编写应用代码本身之前首先实现自动化的单元测试,这些单元测试定义和核实了新代码的需求。
ASP.NET MVC 框架的一些简要细节
在几个星期后,相关代码可以下载之后,我将写一些关于这个新的ASP.NET MVC 框架的深入性的教程贴子(与此同时,想进一步了解它的最佳方式是观看我的Alt.net讲座的录像):
这里是关于ASP.NET MVC 框架的一些简要细节:
- 它将促进清晰的关注分离,可测试性,和TDD。MVC框架中的所以核心契约都是基于接口的,可以轻易地通过mock来模拟(包括基于接口的IHttpRequest/IHttpResponse这些基本的东西)。你可以不用在ASP.NET进程中运行控制器(这使得单元测试很快),就单元测试你的应用。你可以使用你想使用的任何单元测试框架来做单元测试,包括NUnit, MBUnit, MS Test等等。
- 这个框架具有高度的可扩展性和可插拔性。MVC框架中所有的东西都是这样设计的,它们可以被轻易地替换掉或者定制(譬如,你可以插入你自己的视图引擎,路径转向策略(routing policy),参数序列化等等)。它还支持使用现有的依赖注入(dependency injection)和控制反转(IOC)容器模型(Windsor, Spring.Net, NHibernate等等)。
- 它包括一个非常强大的URL映射组件,允许你使用非常干净的URL来建造应用。URL不需要拥有文件扩展,是设计来轻松支持SEO和REST友好的命名模式的。譬如,在我上面的项目中,我可以轻松地把/products/edit/4映射到ProductsController类的Edit方法上,或者把 /Blogs/scottgu/10-10-2007/SomeTopic/ 映射到BlogEngineController类的DisplayPost方法上。
- MVC框架支持将现有的ASP.NET .ASPX, .ASCX,和 .Master 标识文件当作视图模板(view template)之用(这意味着你可以轻松地使用很多现有的ASP.NET特性,象嵌套的母版页,<%= %>块 ,声明式服务控件,模板,数据绑定,本地化等等)。但是,它不使用现有的将交互返回服务器的postback模型,取而代之的是,你将把用户的所有交互转给控制器类来调度,这有助于关注的清晰分离和提高可测试性(这也意味着,在基于MVC的视图内没有viewstate或page的生命周期之说)。
- ASP.NET MVC框架将完全支持象forms/windows认证,URL授权,成员/角色,输出和数据缓存,session/profile状态管理,健康监测,配置系统,以及provider架构等等现有的ASP.NET特性。
结语
如果你正在想使用MVC方式建造你的web应用的话,我认为你会发现这个新的 ASP.NET MVC 框架选项非常干净,而且容易使用。它将允许你在你的应用中很轻易地保持关注分离,而且有助于进行干净的测试和TDD。
几个星期之后,我将撰文说明新的MVC特性的工作原理,以及如何利用它们。
希望本文对你有所帮助,
Scott
【原文地址】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
【原文地址】ASP.NET MVC Framework (Part 2): URL Routing
【原文发表日期】 Monday, December 03, 2007 2:44 AM
上个月,我发表了我要撰写的系列贴子中的第一篇,这些帖子将讨论我们正在开发的新ASP.NET MVC框架。这个系列的第一个贴子建造了一个简单的电子商务产品列表/浏览场景,讨论了MVC后面的高层次的概念,示范了如何从头创建一个新ASP.NET MVC 项目,实现和测试电子商务产品列表的功能。
在今天的贴子里,我将深入讨论ASP.NET MVC框架的路径选择(routing)架构,讨论一些很酷的定制方式,你可以将其用于你应用中的一些更高级的场景。
第一部分的扼要简述
在这个系列的第一部分里,我们创建了一个电子商务网站,呈示了三类URL:
URL格式 | 行为 | URL例子 |
/Products/Categories | 浏览所有的产品分类 | /Products/Categories |
/Products/List/Category | 列出一个分类中的产品 | /Products/List/Beverages |
/Products/Detail/ProductID | 显示一个特定产品的细节 | /Products/Detail/34 |
我们通过创建象下面这样一个ProductsController类来处理这些URL:
在把上面这个类加到我们的应用中后,ASP.NET MVC框架就会把进来的URL自动导向到我们的控制器上的适当的action方法来处理请求。
在今天的贴子里,我们将深入讨论这个URL映射是如何发生的,以及探讨我们可以在ASP.NET MVC框架中利用的更高级的路径选择(routing)场景。我还将示范你如何可以轻松地单元测试URL路径选择场景。
ASP.NET MVC URL路径选择系统都做些什么?
ASP.NET MVC框架包括了一个很灵活的URL路径选择系统,它允许你在应用中定义URL映射规则。路径选择系统有2个主要目的:
- 把进来的URL映射到应用,并把它们做导向,这样,正确的Controller和Action方法执行来处理这些请求
- 构建可以用来回调Controllers/Actions的输出到客户端的URL(例如,表单提交, <a href=""> 链接, 和 AJAX 调用等等)
能够使用URL映射规则来同时处理进来的和输出的URL场景给应用代码添加了许多灵活性。这意味着,如果我们以后想改变应用的URL结构的话(譬如,把 /Products 改名为 /Catalog),我们可以修改应用层次的一套映射规则即可,而不需要改动控制器或视图模板中的任何代码。
默认的ASP.NET MVC URL路径选择规则
在默认情形下,当你使用Visual Studio用ASP.NET MVC Web Application模板来创建一个新项目时,它会往项目里添加一个ASP.NET Application类。这是在Global.asax后台代码中实现的:
ASP.NET Application类允许开发人员处理应用启动/中止以及全局性的错误处理的逻辑。
默认的ASP.NET MVC项目模板自动向该类添加一个Application_Start方法,在其中注册2条URL路径选择规则:
上面的第一条路径选择规则表示,ASP.NET MVC框架在默认情形下,在决定用哪个Controller类来生成实例,调用哪个Action方法时(以及哪些需要传入的参数时),应该使用"[controller]/[action]/[id]"的格式把URL映射到控制器上。
这个默认的路径选择规则就是为什么第一部分中我们的电子商务浏览例程中对URL /Products/Detail/3 的请求自动调用我们的ProductsController类的Detail方法,并且传入3作为id参数值的原因:
上面的第二条路径选择规则,是用来对我们应用的根URL"Default.aspx"做特例处理的(当处理一个应用的根URL的请求时,这个URL有时会被服务器代替"/"来传入)。这个规则确保对我们应用的根"/Default.aspx"或"/"的请求,都会由HomeController类(是在我们使用ASP.NET MVC Web Application项目模板生成一个新的应用时,由Visual Studio自动生成的控制器)里的Index() action方法处理。
理解Route实例
路径选择规则是通过向System.Web.Mvc.RouteTable的Routes集合添加Route实例来注册的。
Route类定义了许多你可以用以配置映射规则的属性。你可以通过“传统的” .NET 2.0属性赋值的方式来设置这些属性:
或者利用VS 2008的C#和VB编译器中的新的对象初始化器特性,更简洁地设置属性:
Route类的Url属性定义了应该用来评估一个路径选择规则是否适用于进来的特定请求的Url匹配规则。它还定义了URL应该如何分割成(tokenized)不同的参数。URL中可替换的参数,是通过 [参数名称] 的句法来定义的。就象在后文论及的那样,我们并不限制于一套固定的“熟知”参数名称,你可以在URL使用任何数目的任意参数。例如,我可以使用一个"/Blogs/[Username]/Archive/[Year]/[Month]/[Day]/[Title]"的URL规则把进来的一个博客贴子的URL进行分割,由MVC框架自动分析成UserName,Year,Month,Day 和 Title参数,并把它们传入我的控制器的action方法中。
Route类上的Defaults属性定义了一个默认值的字典,可以在进来的URL并不包含某个指定的参数值的情形下使用。例如,在上面的URL映射例子中,我们定义了2个默认URL参数值,一个是"[action]" ,另一个是 "[id]"。这意味着,如果应用收到的是 /Products/ 这个URL,在默认情形下,路径选择系统会默认使用“Index”作为ProductsController的action的名称来执行。同样地,如果指定了/Products/List/ ,那么就会使用null字符串作为"ID"参数的值。
Route类的RouteHandler属性定义了在URL被分割成参数,适当的路径选择规则被确定之后,应该用来处理请求的 IRouteHandler 实例。在上面的例子中,我们表示,我们想要使用System.Web.Mvc.MvcRounteHandler类来处理我们配置好的URL。这个额外的步骤存在的原因是,我们想确保URL路径选择系统可以同时用于MVC和非MVC请求的情形。有这个IRouteHandler接口,意味着,我们也能够干净地用于非MVC的请求(例如标准的WebForms,Astoria REST支持等等)。
Route类还有一个Validation属性,在本文的稍后我们会做讨论。这个属性允许我们指定一个路径选择规则匹配需要满足的先决条件。例如,我们可以指定一个路径选择规则应该只适用于一个特定的HTTP动词(允许我们轻松地映射REST命令),或者我们可以对参数值使用正则表达式,来过滤一个路径选择规则是否匹配。
注:在MVC框架的第一个公开预览版中,Route类是不可以扩展的(它只是个数据类),在下一个预览版中,我们正在研究把它做成可扩展的,允许开发人员添加特定场景的路径类(譬如,一个RestRoute子类)来干净利索地添加新的语义和功能。
路径规则的评估
当一个进来的URL被ASP.NET MVC Web应用收到时, MVC框架会对RouteTable.Routes集合中的路径选择规则进行评估,以决定适当的Controller来处理该请求。
MVC框架是按RouteTable规则注册的次序做评估来选择使用哪个Controller的。将进来的URL对每条Route规则做检测,看它是否匹配,如果一个Route规则匹配的话,那么该规则(以及相关联的RouteHandler)将被用来处理进来的请求(所有后面的规则都略过不计)。这意味着你一般要按“最特殊到最不特殊(most specific to least specific,从特殊到一般)”的次序来组织你的路径选择规则。
路径选择场景:自定义查询URL
让我们使用一下现实场景中的自定义路径选择规则来对此做一流程示范,以实现我们的电子商务网站的查询功能为例。
开始,我们往我们项目中添加一个新的SearchController类:
然后,我们在SearchController类中定义2个Action方法。Index()方法用来显示一个查询网页,上有一个文本框,让用户来输入和提交查询文字。Results() action方法则用来处理相应的表单提交,对数据库做查询,然后把结果显示给用户:
使用默认的/[controller]/[action]/[id] URL路径映射规则,我们可以现成使用象下面这样的URL来调用我们的SearchController的行为:
场景 | URL | Action方法 |
查询表单: | /Search/ | Index |
查询结果: | /Search/Results?query=Beverages | Results |
/Search/Results?query=ASP.NET | Results |
注意,根URL /Search 默认映射到Index() action方法的原因是因为在Visual Studio创建一个新项目时,默认添加的 /[controller]/[action]/[id] 的路径定义将默认的action自动设置到“Index"上的(通过Defaults属性):
虽然象 /Search/Results?query=Beverages 这样的URL是完全可行的,我们也许决定对查询结果我们想要稍微好看些的URL。具体来说,我们也许想去掉URL中的“Results”action名称,把要查询的文字作为URL的一部分传入,而不是作为URL的查询字符串的值。例如:
场景 | URL | Action方法 |
查询表单: | /Search/ | Index |
查询结果: | /Search/Beverages | Results |
/Search/ASP.NET | Results |
我们可以通过在默认的 /[controller]/[action]/[id] 规则之前添加2条自定义的URL路径映射规则来启用这些比较好看的查询结果URL,象下面这样:
在前2条规则中,我们现在明确地指定了对应 /Search/ URL的控制器和Action参数。我们表明,"/Search" 应该总是由SearchController上的“Index” action来处理。而任何拥有子URL层次结构的URL (/Search/Foo, /Search/Bar等等 )则总是由SearchController上的 "Results" action 来处理。
上面的第二条路径选择规则表明,在 /Search/ 前缀之后的任何字符应该当作名为"[query]"的参数来处理,这个参数将作为方法参数来传入SearchController上的Results action方法中:
最有可能的,我们还会对查询结果启用分页(我们每次只显示10个查询结果)显示。我们可以通过查询字符串值的方法来实现(譬如,/Search/Beverages?page=2),或者我们也可以把页号嵌在URL中(譬如/Search/Beverages/2)。要支持后面这个做法的话,我们需要做的是,给我们的第二条路径选择规则再加一个额外的可省参数:
注意,上面的新URL规则现在匹配的是“Search/[query]/[page]"。我们还将默认的页号配置为1,万一页号没有包含在URL之中的话(这是通过作为“Defaults”属性值的匿名类型传入的)。
然后我们可以把我们的SearchController.Results action方法更新为接受页号参数作为一个方法参数:
这样,我们就有比较好看的查询URL了(剩下的就是实现这个查询算法,我将把它作为练习留给读者来完成 <g>)。
路径选择规则的验证先决条件
就象我在这个贴子前面提到的,Route类有个Validation属性,允许你添加为使路径选择规则匹配,必须为真的验证先决条件规则(除了URL过滤外)。ASP.NET MVC框架允许你使用正则表达式来验证URL中的参数值,也允许你对HTTP Headers进行评估(根据HTTP动词的不同进行不同的URL路径选择)。
下面是一个我们可以用到象 /Products/Detail/43 这样的URL身上的自定义的验证规则,它指定了其中的ID参数必须是数字(不允许字符串),而且它的长度必须在1到8之间:
如果我们往应用中传入象 /Products/Detail/12 这样的URL,上面的路径选择规则是合法的,但如果传入 /Products/Detail/abc 或 /Products/Detail/23232323232,它就不会匹配。
从路径选择系统构建输出的URL
在本文的前面,我说过ASP.NET MVC框架中的URL路径选择系统负责两件事情:
- 把进来的URL映射到处理的Controllers/Actions上
- 帮着构建可以在以后用来回调Controllers/Actions的输出到客户端的URL(例如,表单提交, <a href="">链接, 和 AJAX 调用等等)
URL路径选择系统有不少辅助方法和类,方便你在运行时动态查看和构建URL(你也可以直接对RouteTable的Route集合进行操作来查看URL)。
Html.ActionLink
在本博客系列的第一部分,我简单地讨论了Html.ActionLink()视图辅助方法。它可以在视图里使用,允许你动态地生成 <a href=""> 超链接。比较酷的是,它可以使用MVC路径选择系统里定义的URL映射规则来生成这些URL。例如,下面2个Html.ActionLink 调用:
automatically pick up the special Search results route rule we configured earlier in this post, and the "href" attribute they generate automatically reflect this: 会自动地使用我们在本贴子前面配置的的特殊查询结果路径规则,它们自动生成的href属性反映了这个情况:
特别地,注意上面,Html.ActionLink的第二个调用自动地把page参数映射成URL的一部分(也注意,第一个调用省略了page参数值,因为它知道服务器端会自动提供默认值)。
Url.Action
除了使用Html.ActionLink外,ASP.NET MVC还有个Url.Action()视图辅助方法。该方法生成原生的字符串URL,然后你可以任何方式来使用它们。例如,下面的代码片段:
会使用URL路径选择系统返回下面这个原生的URL(而不是包装在 <a href=""> 元素里):
Controller.RedirectToAction
ASP.NET MVC还提供了Controller.RedirectToAction()辅助方法,你可以在控制器里使用来进行转向操作(URL是使用URL路径选择系统计算出来的)。
例如,当在控制器里调用下面代码时:
在内部,它会生成一个对Response.Redirect("/Search/Beverages")的调用。
DRY (别重复自己)
上述所有的辅助方法的好处在于它们允许我们避免在我们的控制器和视图逻辑中硬写URL。如果在后来我们决定改变查询URL路径映射规则,从"/Search/[query]/[page]" 改回到 "/Search/Results/[query]/[page]" 或者 "/Search/Results?query=[query]&page=[page]" ,我们只要在一个地方(我们的路径注册代码中)做编辑,就可以轻松搞定。我们不需要改动视图或控制器中的任何代码,就可以捡起新的URL(这就坚持了“DRY原则”)。
使用Lambda表达式从路径选择系统构建输出的URL
前面的URL辅助方法例子使用了VS 2008中VB和C#现在支持的新的匿名类型。在上面的例子中,我们使用了匿名类型来有效地传入一串名称/数值对,用以帮助映射URL(你可以把这想像为生成字典的一个比较干净的方式)。
除了使用匿名类型以动态方式传递参数外, ASP.NET MVC框架还支持使用强类型机制创建action路径的能力,这些强类型机制为URL辅助方法提供了编译时检查和intellisense。这是通过使用泛型和新的VB和C#对Lambda表达式的支持来实现的。
例如,下面这个匿名类型 ActionLink 调用:
也可以写成:
除了写起来简短外,这第二个选项还有类型安全的好处,这意味着你得到对表达式的编译时检查以及Visual Studio的代码intellisense(你还可以使用重构工具对它进行重构):
注意上面,我们是如何使用intellisense挑选出我们想用的SearchController的Action方法的,以及参数是强类型的。生成的URL都是由ASP.NET MVC URL路经选择系统驱动的。
你也许在想,这到底是怎么回事呢?如果你还记得,8个月前,我在博客里讨论Lambda表达式时,我谈到了Lambda表达式既可以编译出成代码代理(delegate),也可以编译成表达式树对象,然后在运行时可以用来分析Lambda表达式。对于Html.ActionLink<T> 辅助方法,我们使用这个表达式树选项,然后在运行时分析对应的lambda,查出它调用的action方法以及相关的参数类型,在表达式中指定的名称和值等。然后我们可以在MVC URL路径选择系统中使用这些信息, 返回合适的URL和相关联的HTML。
重要注意事项: 当使用这Lambda表达式方法时,我们实际上从不运行对应的Controller action方法。例如,下面的代码并不调用我们的SearchController中"Results" action方法:
实际上,它只是返回这个HTML超链接:
如果这个超链接被用户点击的话,它会向服务器发回一个请求,该请求会调用SearchController的Results action方法。
单元测试路径
ASP.NET MVC框架的一个核心设计原则是促进很好的测试支持。 跟MVC框架的其他部分一样,你可以轻松地单元测试路径和路径匹配规则。MVC路径选择系统可以独立于ASP.NET生成实例和运行,这意味着你可以在任何单元测试库里装载和单元测试路径模式(而不用启动web服务器),可以使用任何单元测试框架(NUnit, MBUnit, MSTest等等)。
虽然你可以在你的单元测试中直接单元测试一个ASP.NET MVC应用的全局RouteTable映射集合,但一般来说,让单元测试改变或者依赖于一个全局的状态不是一个很好的主意。一个你可以使用的较好的模式是,把你的路径注册逻辑放在一个象下面这样的RegisterRoutes()辅助方法中,对作为参数传入的RouteCollection进行操作(注:我们也许会把这个模式在下个预览版更新中做成默认的VS模板模式):
然后,你可以编写单元测试,创建自己的RouteCollection实例,调用Application的RegisterRoutes辅助方法,在其中注册应用的路径选择规则。然后,你可以向应用发出模拟请求,核实这些请求确有注册了的正确的控制器和action方法,而不用担心任何副作用:
结语
希望这个贴子提供了关于ASP.NET MVC路径选择架构工作原理的一些细节,以及你如何可以使用它来定制发布在你的ASP.NET MVC应用中的URL的结构和布局。
在默认情形下,在你创建一个新的ASP.NET MVC Web应用时,它会预先定义一个你可以使用的默认的 /[controller]/[action]/[id] 路径选择规则,而不必手工配置或启用什么。这应该允许你不用注册你自己的自定义路径选择规则,就可以建造许多应用。但希望上面的内容示范了,如果你想对你自己的URL格式做自定义结构的话,做起来并不难, MVC框架对此提供了许多的功能和灵活性。
希望本文对你有所帮助,
Scott
【原文地址】ASP.NET MVC Framework (Part 3): Passing ViewData from Controllers to Views
【原文发表日期】 Thursday, December 06, 2007 2:49 AM
【译注】根据Scott Guthrie原文的回复,ASP.NET MVC框架的第一个CTP将于12月7日发布
过去的几个星期内,我一直在写着讨论我们正在开发的新ASP.NET MVC框架的系列贴子。ASP.NET MVC框架是个你可以用来结构化你的ASP.NET web应用,使之拥有清晰的关注分离,方便你单元测试代码和支持TDD流程的可选方法。
这个系列的第一篇建造了一个简单的电子商务产品列表/浏览网站。它讨论了MVC后面的高层次的概念,示范了如何从头创建一个新的ASP.NET MVC项目,实现和测试这个电子商务产品列表功能。系列的第二篇对ASP.NET MVC框架的URL路径选择(routing)架构做了深入探讨,讨论了它的工作原理以及你如何使用它来处理更高级的URL路径选择场景。
在今天的帖子里,我将讨论控制器是如何与视图做交互的,具体来说,我将讨论你可以把数据从控制器传到视图以显示返回到客户端的回复的各种方式。
第一部分的扼要简述
在这个系列的第一部分,我们创建了一个电子商务网站,实现了基本的产品列表/浏览支持。我们是用ASP.NET MVC框架实现这个网站的,这个方法会很自然地将代码结构化为独特的控制器,模型和视图组件。
当浏览器向我们的网站发送一个HTTP请求时,ASP.NET MVC框架将使用它的URL路径选择引擎,把进来的请求映射到一个控制器上的action方法来处理它。在基于MVC的应用中的控制器负责处理进来的请求,处理用户输入和交互,执行基于这些输入和交互的应用逻辑(获取或更新存储在数据库中的模型数据等等)。
到生成返回到客户端的HTML回复的时候,控制器一般是与“视图”组件合作,这些视图组件是以独立于控制器的单独的类或模板的形式实现的,其目的是完全注重于封装显示逻辑。
视图不应该含有任何应用逻辑或数据库访问代码,所有的应用/数据逻辑应该由控制器类来处理。这么划分的动机是帮助强制你的应用/数据逻辑与界面生成代码间的清晰分离。同时这也方便你独立于你的界面显示逻辑来单元测试你的应用/数据逻辑。
视图应该只使用从控制器传过来的特定于视图的数据来生成输出。在ASP.NET MVC框架中,我们称这个特定于视图的数据为“ViewData”。这个博客的其他部分将讨论你可以用来将ViewData从控制器传递给视图来生成显示的一些不同方法。
一个简单的产品列表场景
为帮助说明我们可以用来把ViewData从控制器传递给视图的一些技术,让我们来建造一个简单的产品列表网页:
我们将用一个CategoryID整数来过滤我们想要显示在页面上的产品。注意上面我们是如何把CategoryID嵌在URL中的(例如,Products/Category/2 或 /Products/Category/4 )。
然后,我们的产品列表网页显示了2个不同的动态内容元素。第一个元素是我们要显示的分类的文本名称(例如,Condiments-调味品),第二个元素是一个HTML <ul><li/></ul> 产品名字列表。我在上面的屏幕截图中对这2个元素用红笔画了圈。
在下面,我们将看一下我们可以使用的2个不同的方法来实现ProductsController类,这个类处理进来的请求,获取处理请求所需的数据,然后将这个数据传给一个List视图来显示。我们要研究的第一个方法是用后期绑定的字典对象传递这个数据,第二个方法则使用强类型类的方式来传递这个数据。
方法 1:使用 Controller.ViewData 字典来传递ViewData
Controller基类有个ViewData字典属性,可以用来填充你要传给视图的数据。你使用键/值模式将对象加入 ViewData 字典。
下面是个ProductsController类,其中的Category action方法实现了我们上面的产品列表场景。注意,它是如何使用分类的ID参数来查询该分类的文本名称,以及获取该分类中的产品列表的。它使用“CategoryName”和“Products”两个键将这两个数据存储在Controller.ViewData 集合中:
然后,我们上面的Category action方法调用 RenderView("List") 来表示它要用哪个模板来做显示。当你象这样调用RenderView时,它会将ViewData字典传给视图,以显示对应的回复。
实现我们的视图
我们将使用居于我们项目的\Views\Products目录下的List.aspx文件来实现我们的List视图。这个 List.aspx 将继承 \Views\Shared 文件夹中的Site.Master母版页中的布局(在你创建一个新的视图网页时,你可以在 VS 2008 中,右击,选择添加新项->MVC视图内容网页来接连一个母版页):
当我们使用MVC视图内容网页模板来创建List.aspx网页时,它不是从通常的 System.Web.UI.Page 类继承而来,而是从System.Web.Mvc.ViewPage 基类继承而来(是现有的Page类的一个子类):
ViewPage基类提供一个ViewData字典属性,我们可以在视图网页里访问由控制器添加的数据对象。然后我们可以取出这些数据对象,使用它们来显示HTML输出,可以用服务器控件的方式,或者用 <%= %> 显示代码的方式。
使用服务器控件来实现我们的视图
下面是一个如何使用现有的<asp:literal> 和 <asp:repeater>服务器控件来实现我们的HTML界面的例子:
我们可以用下面的后台代码类将 ViewData 绑定到这些控件之上(注意我们是如何使用ViewPage的ViewData字典来实现的 ):
注: 因为页面上没有 <form runat="server">,是不会输出 view-state 的。上面的控件也不会自动生成任何ID值,这意味着你对输出的HTML有完全的控制。
使用 <%= %> 代码来实现我们的视图
如果你更喜欢使用行内代码来生成输出的话,你可使用下面的 List.aspx 来实现跟上面完全一样的结果:
注: 因为ViewData的类型是含有“objects”的字典,为了对它使用foreach语句,我们需要将ViewData["Products"]的类型转换成 List<Product> 或者 IEnumerable<Product>。我在页面上引用了System.Collections.Generic 和 MyStore.Models 命名空间 以避免输入 List<T> 和 Product 类型的完整名称。
注: 上面使用了“var”关键词,这是VS 2008中新的 C# 和 VB “类型推断”特性的一个例子(在这里阅读我以前的相关贴子)。因为我们将ViewData["Products"] 转换成了 List<Product>,我们在 List.aspx 文件中的 prduct 变量上得到了完整的intellisense:
方法 2:使用强类型类来传递ViewData
除了支持后期绑定的字典方法外,ASP.NET MVC框架还允许你把强类型的ViewData对象从控制器传递给你的视图。使用这个强类型的方法有几个好处:
- 避免使用字符串来查询对象,得到对你的控制器和视图代码的编译时检查
- 避免需要在使用象C#这样的强类型语言中明确转换ViewData对象字典中的值
- 在你的视图网页的标识文件以及后台代码文件中得到你的ViewData对象的自动代码intellisense
- 可以使用代码重构工具来帮助自动化对整个应用和单元测试代码库的改动
下面是一个强类型的ProductsListViewData类,封装了 List.aspx 视图显示我们的产品列表所需的数据,它含有 CategoryName 和 Products 属性(是通过使用新的C#自动属性支持来实现的):
然后我们可以更新我们的 ProductsController 实现来使用这个对象,把一个强类型的ViewData对象传给我们的视图:
注意上面,我们是如何通过 RenderView() 方法的一个额外的参数,把我们的强类型 ProductsListViewData 对象传给View的。
把视图的ViewData字典与强类型的ViewData对象一起使用
前面我们编写的 List.aspx 视图实现会继续和我们更新过的 ProductsController 协作,不需改动代码。这是因为,当把一个强类型的 ViewData 对象传递给继承自 ViewPage 的视图类时,ViewData 字典会自动使用反射对强类型的对象的属性做查询取值。所以我们象下面这样的视图中的代码:
会自动使用反射来从强类型的 ProductsListViewData 对象中获取 CategoryName 属性,这个对象是我们在调用 RenderView 方法时传入的。
使用ViewPage<T>基类来对ViewData强类型化
除了支持基于字典的ViewPage基类外,ASP.NET MVC框架中还发布有基于泛型的 ViewPage<T> 实现。如果你的视图是从 ViewPage<T> 继承而来,这里T表示是控制器传给视图的 ViewData 的类型,那么 ViewData 属性就将是使用了这个T类的强类型属性。
例如,我们可以更新我们的 List.aspx.cs 后台代码类,不是从ViewPage继承来,而是继承自 ViewPage<ProductsListViewData> :
这么做之后,页面上的 ViewData 属性将会从一个字典变成属于 ProductsListViewData 类型。这意味着,我们现在可以不再使用基于字符串的字典来查阅获取数据,而是可以使用强类型的属性了:
然后,我们可以使用服务器控件的方法,或者 <%= %> 显示的方法来生成基于这个ViewData的HTML。
使用服务器控件来实现 ViewPage<T>视图
下面是一个例子,我们可以使用<asp:literal> 和 <asp:repeater>服务器控件来实现我们的HTML界面。这是我们使用继承自 ViewPage 的 List.aspx 网页时所使用的完全一样的标识:
下面是相应的后台代码。注意,因为我们是从 ViewPage<ProductsListViewData> 继承而来的,我们可以直接访问它的属性,而不要对任何东西做类型转换(什么时候我们决定对其中一个属性改名的话,我们还将得到重构工具的支持):
使用 <%= %> 代码实现我们的 ViewPage<T> 视图
如果你更喜欢使用行内代码来生成输出的话,你可以象下面这样在 List.aspx 中达成跟上面一样的结果:
使用 ViewPage<T> 方法,我们现在不再需要对 ViewData 使用字符串查阅了。更重要的是,注意上面,我们不再需要对任何属性做类型转换了,因为它们已经是强类型的。这意味着,我们可以编写 foreach (var product in ViewData.Products) ,而不用对 Products 做类型转换。我们还在循环中的 product 变量上得到了完整的intellisense:
结语
希望本贴子提供了关于控制器如何把数据传递给视图以显示返回到客户端的回复的一些细节。你可以使用后期绑定的字典,或者使用强类型的方式来达成这个目的。
第一次试着建造MVC应用时,你很可能发现把应用控制器的逻辑和生成界面的代码分离开来的概念有点怪。你大概要花上一段专门的时间来多建造些应用,你才会感到习惯,把自己的思路转向到处理一个请求,执行所有的应用逻辑,把建造界面回复所需的 viewdata 包装起来,然后交给单独的一个视图页面去显示的观念上去。 重要事项:如果这个模型对你来说并不感觉舒服,那么别用它,MVC的方法纯粹是可选的,我们并不认为这是每个人都想要用的东西。
但这个划分应用的好处以及其后的目标在于,它允许你独立于你的界面显示代码,来运行和测试你的应用和数据逻辑。这极大地方便你为你的应用开发全面的单元测试,以及在建造应用时使用TDD(测试驱动开发)的流程。在以后的贴子里,我会对此做更深入的讨论,以及讨论你可以用来轻松测试代码的最佳实践。
希望本文对你有所帮助,
Scott
【原文地址】ASP.NET MVC Framework (Part 4): Handling Form Edit and Post Scenarios
【原文发表日期】 Sunday, December 09, 2007 4:42 AM
过去的几个星期内,我一直在写着讨论我们正在开发的新ASP.NET MVC框架的系列贴子。ASP.NET MVC框架是个你可以用来结构化你的ASP.NET web应用,使之拥有清晰的关注分离,方便你单元测试代码和支持TDD流程的可选方法。
这个系列的第一篇建造了一个简单的电子商务产品列表/浏览网站。它讨论了MVC后面的高层次的概念,示范了如何从头创建一个新的ASP.NET MVC项目,实现和测试这个电子商务产品列表功能。系列的第二篇对ASP.NET MVC框架的URL路径选择(routing)架构做了深入探讨,讨论了它的工作原理以及你如何使用它来处理更高级的URL路径选择场景。 第三篇讨论了控制器是如何与视图做交互的,特别地讨论了你可以把视图数据从控制器传给视图以显示返回到客户端的回复的各种方法。
在今天的帖子里,我将讨论你可以用MVC框架来处理表单输入和提交场景的各种方法,以及讨论一些你可以用来简化数据编辑场景的HTML辅助方法。点击这里下载我们将在下面为解释这些概念而建造的完整的应用的源代码。
表单输入和提交场景
为示范如何在ASP.NET MVC框架中处理表单输入和提交场景的一些基本原则,我们将建造一个简单的产品列表,产品生成,和产品编辑场景。它将拥有三个核心的用户体验:
按类列出的产品列表
通过导航到/Products/Category/[CategoryID] 这样的URL,用户将能看到在某个特定产品分类内的所有产品的列表:
添加新产品
用户将能通过点击上面的“添加新产品”的链接往商店里添加一个新产品。点击之后,会转到/Products/New URL,在这里,系统将提示用户输入要添加的新产品的细节:
在点击Save(保存)之后,产品就会添加到数据库中,然后就会转向返回到产品列表网页。
编辑产品
在产品列表网页上,用户可以点击每个产品旁边的“Edit”(编辑)链接。这会转到/Products/Edit/[ProductID] URL,在这里,用户可以改动产品的细节,然后点击Save按钮,往数据库里更新:
我们的数据模型
我们将使用SQL Server Northwind样品数据库来存储我们的数据。然后我们将使用.NET 3.5内置的LINQ to SQL对象关系映射器(ORM)来对Product, Category, 和 Supplier对象进行建模,这些对象代表了我们的数据库数据表中的记录行。
一开始,在ASP.NET MVC项目中,右击/Models子目录,选择“添加新项” -> “LINQ to SQL 类”,调出 LINQ to SQL ORM 设计器来对我们的数据对象建模:
然后我们将在项目中创建一个NorthwindDataContext部分类(partial class),向里面添加一些辅助方法。我们定义这些辅助方法有2个原因: 1)避免在我们的Controller类中直接嵌入我们的LINQ查询,2) 将允许我们在将来更容易地改变我们的控制器以使用dependency injection(依赖注入)。
我们将添加的NorthwindDataContext辅助方法是象下面这样的:
想进一步了解LINQ和LINQ to SQL的话,请参阅我这里的LINQ to SQL系列。
建造我们ProductsController控制器
我们将使用单一控制器类来实现这三个核心用户浏览体验,我们将称这个控制器类为“ProductsController”(在Controllers子目录上右击,选择“添加新项” -> “MVC 控制器”来创建这个类:
我们的 ProductsController 类将通过实现"Category", "New", 和"Edit" 等action方法来处理象/Products/Category/3, /Products/New, 和/Products/Edit/5这样的URL:
想了解这些URL是如何导向到 ProductsController 类的action方法上的话,请阅读我的ASP.NET MVC系列的第一部分和第二部分。在本文的例子里,我们将使用默认的/[Controller]/[Action]/[Id]路径映射规则,这意味着我们不必配置什么东西,路径导向就会自动发生。
我们控制器的Action方法将使用三个视图网页,用以显示输出。"List.aspx", "New.aspx", 和 "Edit.aspx" 网页将居于 \Views\Products 子目录下,这些网页将基于\Views\Shared目录中的Site.Master母版页上。
实现按类列出的产品列表
我们要实现的网站的第一部分将是产品列表URL (/Products/Category/[CategoryId]) :
我们将使用我们的ProductsController类上的"Category" action方法来实现这个功能。我们将使用LINQ to SQL DataContext类,和我们往其中添加的GetCategoryById辅助方法,来获取一个Category对象,该对象代表了由URL (譬如, /Products/Category/3) 指定的某个特定分类。然后我们将该Category对象传给"List"视图来从中生成回复:
在实现我们的List视图时,我们首先将更新我们网页的后台代码,从ViewPage<Category>继承而来,这样页面的ViewData属性将是从我们的控制器传过来的Category对象的类型(第三部分对此有详细讨论):
然后我们将象下面这样实现List.aspx:
上面的视图在页面上方显示了分类名称,然后显示了分类内的所有产品的项目列表。
在项目列表的每个产品旁边,有个 "Edit" 链接。我们是用在第二部分中讨论过的Html.ActionLink辅助方法来显示这些HTML超链接(譬如,<a href="/Products/Edit/4">Edit</a>)的,在"Edit"链接被点击后,用户将被导向到"Edit"action方法。然后我们还将使用Html.ActionLink辅助方法在页面底部生成一个<a href="/Products/New">Add New Product</a>链接,在该链接被点击后,用户将被导向到"New" action方法。
当我们访问 /Products/Category/1 URL时,在浏览器中查看源码的话,你会注意到我们的ASP.NET MVC应用输出了非常干净的HTML和URL标识:
实现添加新产品(第一部分-背景知识)
现在让我们来实现网站的“添加新产品”表单提交功能,最终我们想要用户在访问/Products/New URL时看到象下面这样的显示:
在ASP.NET MVC框架中,表单输入和编辑场景一般是通过在Controller类上呈示2个Action方法来处理的。第一个Controller Action方法负责发送含有要显示的初始表单的HTML。第二个Controller Action方法则负责处理从浏览器发回的任何表单提交。
例如,对上面的“添加产品”屏幕,我们会选择在ProductsController上的2个不同action中来实现:一个叫"New",另一个叫"Create"。/Products/New URL负责显示一个带有HTML文本框和下拉框控件的空白表单,让用户输入新产品的细节。然后,这个网页上的HTML <form>元素将其action属性设置为 /Products/Create URL。这意味着当用户点击表单提交按钮时,表单的输入将被发送到"Create" action方法上来处理和更新数据库。
实现添加新产品(第二部分 - 第一种方法)
下面是我们可以用来实现ProductsController的一个初始实现。
注意上面,在涉及产品生成过程中,我们有2个action方法, - "New" 和 "Create"。 "New" action方法只是简单地向用户显示一个空白表单。"Create" action方法则处理从表单提交过来的值,根据这些值在数据库中生成一个新产品,然后将客户转向到产品的分类列表网页。
发送到客户端的HTML表单,是在由"New" action方法调用的"New.aspx"视图里实现的。这个视图的一个初始实现(每个输入都用了文本框)看上去象下面这样:
注意上面,我们在网页上使用了标准的 HTML <form> 元素,而不是form runat=server。表单的"action"属性被设置为ProductsController上的"Create" action方法。在页面底部的<input type="submit">元素被点击时,提交就会发生,之后,ASP.NET MVC框架就会自动将ProductName, CategoryID, SupplierID 和 UnitPrice值映射为方法参数,传给ProductsController上的 "Create" action方法:
至此,我们运行网站时,就有了最基本的产品输入功能:
实现添加新产品 (第三部分 - 使用HTML辅助方法实现下拉框)
我们在前面一节里创建的产品输入屏幕是可行的,但不是很友好。具体来说,它要求用户知道正输入的产品的原始CategoryID和SupplierID成员。我们需要通过显示内含可读名称的HTML下拉框来修正这个问题。
第一步,将修改ProductsController来向视图里传人2个集合,一个内含现有的分类列表,另一个内含产品供应商列表。我们将通过生成一个封装这些列表的强类型的ProductsNewViewData类,然后将它传给视图来达成这个目的(你可以在第三部分中了解有关详情):
然后我们将更新 "New" action 方法来填充这些集合,然后将它们作为ViewData传给 "New" 视图:
然后在我们的视图里,我们可以使用这些集合来生成 HTML <select> 下拉框。
ASP.NET MVC HTML 辅助方法
我们可以用来生成下拉框的一个方法是在HTML里手工生成内含 if/else 语句的 <% %> for-循环。这会给与我们对HTML的完全控制,但HTML会很乱。
一个你可以使用的干净得多的方法是利用ViewPage基类上的"Html"辅助属性。这是个方便对象,呈示了一套HTML辅助界面方法,用于自动化HTML界面的生成。例如,在本帖子的前面,我们使用了 Html.ActionLink辅助方法来生成 <a href=""> 元素:
HtmlHelper对象(以及我们将在以后的教程里讨论的AjaxHelper对象)是特地设计可以通过使用"扩展方法"(VS 2008中VB和C#的一个新语言特性)来轻松地扩展的。这意味着,任何人都可以为这些对象生成他们自己的自定义辅助方法,共享这些方法,为你所用。
在ASP.NET MVC框架将来的预览版中,我们将提供几十个内置的HTML和AJAX辅助方法。在第一个预览版中,只有"ActionLink"方法是内置于System.Web.Extensions(目前实现核心ASP.NET MVC框架的程序集)中的。但我们还将有一个单独的 "MVCToolkit" 下载,你可以加到你的项目中,来得到你可以在第一个预览版中使用的的几十个辅助方法。
要安装MVCToolkit HTML辅助方法的话,只要将MVCToolkit.dll程序集添加为你的项目的引用即可:
重新编译你的项目,然后下一次你键入 <%= Html. %> 的话,你将看到许许多多你可以使用的额外界面辅助方法:
为生成HTML <select>下拉框,我们可以使用Html.Select()方法。每个方法都有重载的版本,在视图里有完整的intellisense:
我们可以更新我们的"New"视图,用下面的代码,使用Html.Select选项来显示使用CategoryID/SupplierID属性作为值,CategoryName/SupplierName作为显示文字的下拉框:
这会在运行时为我们生成适当的<select> HTML标识:
在/Products/New屏幕上给用户一个方便的方式来选择产品分类和供应商:
注: 因为我们还是在向服务器提交CategoryID和SupplierID值,所以我们根本不用更新ProductsController的Create Action方法来支持这个新的下拉框界面,这个方法还是工作的。
实现添加新产品(第四部分 - 使用UpdateFrom方法清理Create代码)
我们的ProductsController的"Create" Action方法负责处理我们的“添加产品”场景的表单提交。目前它是以action方法参数的方式来处理进来的表单参数的:
这个方法是可行的,但对于涉及大量值的表单,Action方法的签名就会开始变得有点难读。而且,上面将所有进来的参数值设置到新的Product对象上的代码有点长,而且单调。
如果你引用了MVCToolkit程序集,你可以利用在System.Web.Mvc.BindingHelpers命名空间下实现的一个有用的扩展方法,来对此代码作些清理。这个扩展方法叫做“UpdateFrom”,可以用在任何 .NET 对象上。它接受一个字典作为参数,然后,它会对任何匹配该对象的公开属性的键,自动对本身进行属性赋值。
例如,我们可以重写我们上面的Create action方法,来使用UpdateFrom方法,象这样:
注: 如果你因为安全的原因,想要更明确些,只允许某些属性可以更新的话,你还可以向UpdateFrom方法传入一个可以更新的属性名称的字符串数组:
实现编辑产品功能(第一部分 - 背景知识)
现在让我们来实现网站“编辑产品”的功能。我们最终想要用户在访问/Products/Edit/[ProductID] URL时看到象下面这样的屏幕:
跟上面的“添加新产品”表单提交例子一样,我们将使用2个ProductsController Action方法来实现这个表单编辑交互,我们将称这2个方法为"Edit"和"Update":
"Edit" 会显示产品表单,"Update"会被用来处理表单的提交行动。
实现编辑产品功能(第二部分 - Edit Action)
我们将通过实现ProductController的Edit action方法来开始启用我们应用的编辑功能。当我们在本贴子的开头创建产品列表网页的时候,我们是这么建造的,Edit action将接受一个作为URL一部分的id参数(譬如,/Products/Edit/5):
我们想要Edit Action方法从数据库中获取适当的产品对象,以及现有的产品供应商和分类集合(这样,我们可以在我们的编辑视图里实现这些东西对应的下拉框)。我们将使用下面的ProductsEditViewData对象来定义一个强类型的视图对象来代表所有这些数据:
然后,我们可以实现我们的Edit action方法来填充这个viewdata对象,在"Edit" 视图中显示:
实现编辑产品功能(第三部分 - Edit 视图)
我们可以使用下述方法来实现Edit.aspx视图网页:
注意我们是如何同时使用上面例子中的Html.TextBox和Html.Select辅助方法来的。这2个方法都是来自MVCToolkit.dll程序集中的扩展方法。
注意Html.Select辅助方法有个重载版本,允许你指定下拉框中的选定值是什么。在下面的代码片断中,我表示我要Category下拉框根据编辑产品目前的CategoryID值自动选择某一项:
最后,注意我们是如何使用Url.Action()辅助方法来设置<form>元素的action属性的:
Url.Action和Html.ActionLink这2个辅助方法都使用了ASP.NET MVC框架的路径选择引擎来生成URL(参阅第二部分以了解URL生成原理的细节)。这意味着,如果我们改变我们网站的编辑功能的路径选择规则的话,我们不需要改动控制器或视图中的任何代码。例如,我们可以将我们的URL做重新映射,换掉/Products/Edit/1,而是使用象/Products/1/Edit这样更具RESTful的URL的话,上面的控制器和视图代码不用做改动,而依旧会工作。
实现编辑产品功能(第四部分 - Update Action)
最后一步是实现ProductController类上的"Update" action方法:
跟前面的"Create" action方法一样,我们将利用"UpdateFrom"扩展方法来从请求中自动填充我们的产品对象。但注意,填充的不是一个空对象,我们使用了一个模式,先从数据库中获取老的值,然后对它应用用户做的改动,然后更新到数据库中。
编译完毕之后,我们重新定向到产品列表网页,自动设置 /Products/Category/[CategoryID],以匹配我们正在操作的产品的保存的状态。
结语
希望本帖子提供了在ASP.NET MVC框架中如何处理表单输入和提交场景的一些细节,还提供了你可以如何处理和结构化常见数据输入和编辑场景的一些背景。
点击这里下载一个内含我们在上面建造的完整应用源代码的.ZIP 文件。
在将来的帖子里,我将讨论如何处理表单输入和编辑场景中数据验证和错误复原的情形。我将讨论一些促进快速应用开发的内置的数据和安全支架(scaffolding)。我将讨论你如何在MVC框架中使用ASP.NET AJAX进行启用AJAX的编辑。我还将对如何单元测试控制器和向控制器添加依赖注入做深入的探讨。
希望本文对你有所帮助,
Scott