Scott的ASP.net MVC框架系列文章之四:处理表单数据(2)
时间:2011-05-16 来源:刺猬的温驯
前几周我发表了一系列文章介绍我们正在研究的ASP.NET MVC框架。ASP.NET MVC框架为你提供了一种新的开发Web应用程序的途径,这种途径可以让应用程序变得更加层次清晰,而且更加有利于对代码进行单元测试和支持TDD(测试驱动开发)开发。
这一些列的第一篇文章创建了一个简单的电子商务产品列表/浏览站点。他涉及到了MVC背后的高层次概念并演示了一个ASP.NET MVC项目从设计到实现的过程和对产品列表功能的测试。该系列的第二篇文章深入介绍了ASP.NET MVC框架的URL映射机制并针对其工作原理和更复杂URL映射的处理进行了深入讨论。第三篇文章讨论了控制类和视图的交互,并专门介绍了从控制类向视图中传递数据用于生成客户端呈现的方法。
今天这篇文章将要讨论如何使用ASP.NET MVC框架处理表单输入提交操作.,同时讨论一些可以用于简化数据编辑的HTML辅助扩展方法。点击这里下载完整的应用程序代码以便于你理解这些概念。
表单输入和提交操作
为了说明如何使用ASP.NET MVC 框架对表单编辑和提交进行基本操作,我们将要建立一个简单的产品列表/产品创建和产品编辑应用。他将为用户提供三种核心的功能:
根据目录列出产品
用户可以通过/Products/Category/[CategoryID]地址访问某一目录下的产品列表:
添加新产品
用户点击“Add New Produt”链接可以向商店中添加新产品,该链接将用户链接到/Products/New地址,允许用户输入添加产品的详细信息。
点击保存按钮,产品将被添加到数据库中,页面返回到产品列表页。
编辑产品
在产品列表页用户可以点击每项产品旁边的“Edit”链接到/Products/Edit/[ProductID]地址,允许用户修改对应产品的详细信息,点击保存按钮时将新信息保存到数据库中。
数据模型
我们使用SQL Server的Northwind示例数据库存储我们的数据。我们将对产品(Product)、目录和(Category)供应(Supplier)商对象及其数据库表中对应的行应用.NET3.0中内置的LINQ to SQL 实体关系映射机制。
右单击ASP.NET MVC项目中的/Model子目录,选择“添加新项目”->“LINQ to SQL 类”,打开LINQ to SQL ORM 设计器并对数据对象建模:
然后我们要在项目中创建NorthwindDataContext分布类并添加一些辅助方法。我们定义这些辅助方法出于两点考虑:1)避免直接向Controller类中嵌入LINQ查询语句2)为以后对Controller控制类使用依赖项(Dependency injection)注入做准备。
我们要添加的NorthwindDataContext辅助方法如下:
点击这里查看LINQ to SQL 系列文章了解更多关于LINQ to SQL的知识。
创建ProductsController类
我们将使用ProductsController类实现前面提到的3种核心功能(通过右单击Controllers子文件夹并选择“添加新项目”->“MVC控制类”创建):
ProductsController类将要通过实现Category, New和Edit方法处理类似/Products/Category/3, /Products/New和/Products/Edit/5的URLqingqiu .
请阅读本系列的Part 1和Part 2了解更多关于URL到控制方法映射的内容。
控制类方法将通过三个视图(Views)页面实现生成输出内容。/Views/Products子目录下的”List.aspx””New.aspx”和”edit.aspx”页面,他们共同使用\Views\Shared目录下的Site.Master母板页。
依据目录显示产品列表的显示
我们首先实现产品列表的URL请求/Products/Category/[CategoryId]:
我们使用ProductsController中的”Category”方法实现该功能,并使用LINQ to SQL DataContext类中的GetCategoryById辅助方法获取在URL(例如/Products/Category/3)中指定的某一目录对象,然后将该目录对象传入”List”视图,并显示输出:
为了实现List视图,首先将该页面的后台代码文件修改为继承自ViewPage<Category>基类,将页面的接收到的来自Controller控制类的ViewData属性类型定义为Category(Part 3中有详细讨论):
List.aspx的实现如下:
上面的视图在页面顶部显示了目录名称,并显示该目录的产品列表。
列表中每一产品项的旁边为“编辑”链接。我们将使用Part 2中提到的Html.ActionLink()辅助方法来生成HTML链接(例如:<a href=”/Products/Edit/4”>Edit</a>),点击该链接时进行编辑操作。同时我们要使用该方法在页面底部生成<a href=”/Products/New”>Add New Product</a>链接,点击该链接进行“New”操作。
当我们打开/Products/Category/1地址并在浏览器中查看源代码时,你将会发现我们的ASP.NET MVC应用程序迁入了整洁的HTML和URL标记:
添加新产品功能的实现(1- 背景)
我们接下来要实现“添加新产品”功能。我们最终希望用户访问/Products/New地址时可以看到下面的界面:
在ASP.NET MVC框架中表单输入和编辑通常是由Controller类中的两个方法实现的。第一个方法用于产生并发送包含表单的HTML代码,另一个用于处理浏览器发回的提交信息。
例如,对于上面的“添加产品”表单,我们可以通过ProductsController中的两个方法来实现:“New”和“Create”。/Products/New请求将用于显示包含用于输入产品信息的HTML文本框和下拉菜单控件的空表单。该页面的HTML <form>元素的action属性将设置为/Products/Create,这意味着当用户点击提交表单按钮时,表单的输入信息将被发往“Create”方法处理并更新数据库。
添加新产品的实现(2-第一种方式)
下面是我们可以用于ProductsController类的实现:
注意上面的代码,在产品创建过程中我们要使用到两个方法“New”和“Create”。“New”方法向用户呈现一个空白表单,“Create”方法处理表单中提交的数据,在数据库中创建一个新的产品,并将浏览器定位到产品列表页。
“News.aspx”定义了发往客户端的HTML表单并被New方法调用。其实现(均使用TextBox实现)如下:
注意我们是如何使用标准的HTML <form>元素的(不是form runat=server)。表单的action属性将数据提交交给ProductController类的Create方法处理。当底部的<input type=”submit”>元素被点击时提交数据,此时ASP.Net MVC 框架将自动将ProductName, CategoryID, SupplierID和UnitPrice的值作为Create方法的参数传入。
运行已经具有基本产品项功能的站点:
添加新产品的实现(3-对下拉列表使用HTML辅助)
我们前面已经完成了产品项页面,但界面还不够友好。因为用户在添加产品时必须知道原始的CategoryID和SupplierID。我们希望显示HTML下拉列表来显示有好的目录名称来解决该问题。
我们的第一步是修改ProductsController使其向视图中传递两个集合数据,一个包含可以用的目录列表,另一个可以是可用的供应商列表。我们创建一个强类型的ProductsNewViewData类封装这些数据,并传递给视图(Part 3详细介绍了该部分内容):
我们要更新“New”方法处理这些集合数据并作为ViewData传递给“New”视图:
在我们的视图中我们可以使用这些集合数据生成HTML的下拉列表。
ASP.NET MVC HTML辅助方法
我们可以使用在HTML中使用<%%>嵌入包含if/else的for循环语句来生成下拉列表,通过该种方式我们可以对HTML具有完全的控制权,但这样会使得HTML代码变得混乱。
你也可以选择使用ViewPage基类中的“Html”辅助属性。该对象实现了一些列HTML UI辅助方法自动完成Html UI生成。例如前面提到的使用Html.ActionLink辅助方法生成<a href=’’>元素:
HtmlHelper对象(包括后面将要提到的AjaxHelper对象)被设计成易于使用“扩展方法Extension Methods”(VS2008中VB和C#的新语言特性)使得任何人都可以在该对象中创建自定义的辅助方法并使用。
在以后的ASP.NET MVC框架中我们将提供更多内置的HTML和AJAX辅助方法。在第一个预览版本中只是在System.Web.Extensions(ASP.Net MVC框架当前实现的程序集)嵌入了“ActionLink”方法。我们同时提供了一个独立的MVCToolkit下载,你可以使用它像项目中更多的辅助方法。
使用MVCToolkit HTML辅助方法,只需要将MVCToolkit.dll程序集添加到你的项目引用中:
重新编译项目,下次使用<%=HTML.%>时,你将会看到更多的UI辅助方法:
我们可以使用Html.Select()方法来构建HTML <select>下拉列表,每一个方法包含多个重载版本,并都具有智能感知支持:
我们可以在“New”视图中使用Html.Select选项显示下拉列表,并用CategoryID/SupplierID作为值属性,用CategoryName/SupplierName作为显示文字属性,代码如下:
在运行时将产生对应的<select> HTML标记:
这使用户可以更简单的选择产品目录和供应商信息:
注意:因为我们仍然提交CategoryID和SupplierID值,因此我们不需要针对新的下拉列表界面更新ProductsController的Create方法。
添加新产品的实现(4-使用UpdateForm方法结束创建过程)
我们的ProductsController类的Create方法用于处理添加产品表单提交的数据,目前该方法通过将表单数据作为参数传入方法:
虽然这种方式要在方法的签名中指定大量的参数,但这种方式仍然是可以正常工作的,上面的代码将所有传入的表单值传递给Products对象,代码仍然有些复杂。
如果你引用了MVCToolkit程序集,你可以选择使用System.Web.Mvc.BindingHelpers命名空间中实现的一个Extension Method来简化代码。该方法是”UpdateForm”并可以引用于所有.NET对象。它包含了一个字典值作为参数,并将自动给对象中具有与键名同名的属性进行复制。
例如,我们可以使用UpdateForm方法重写Create方法:
注意:如果你希望处于安全原因更加严谨并只允许某些属性被赋值,你可以选择向UpdateForm方法中传入一个包含需要更新的属性名称的字符串数组:
编辑产品功能的实现(1-背景)
现在来实现站点的产品编辑功能。我们希望最终用户看到访问/Products/Edit/[ProductID]地址是看到下面的界面:
与上面添加产品的表单相似,我们使这个表单与ProductsController中的“Edit”和“Update”方法交互:
“Edit”显示产品表单,“Update”用于处理提交数据。
编辑产品功能的实现(2-Edit方法)
我们通过实现ProductsController中的Edit方法来实现应用程序的编辑产品功能。当我们完成创建产品功能并编译后,Edit()方法将要求URL 的一部分作为一个id参数(例如:/Products/Edit/5):
我们希望Edit方法可以从数据库中获取指定的产品对象和可用的供应商和产品集合(用于在编辑视图中显示下拉列表),我们可以定义一个强类型的ProductsEditViewData视图数据对象代表。
我们可以通过实现Edit方法来处理ViewData对象并通过“Edit”视图进行呈现:
编辑产品功能的实现(2-编辑视图)
我们可以通过下面的方式实现Edit.aspx视图页:
注意上面的例子中我们是如何使用Html.TextBox和Html.Select辅助方法的,这两个辅助方法均来自MVCToolkit.dll 程序集。
注意Html.Select辅助方法提供了一个重载方法允许你指定下拉菜单中的选中值,在下面的代码段中,产品目录下拉列表中的选中项目自动设置为当前产品的目录的选中值:
最后,注意我们使用Url.Action()辅助方法设置<form>元素的action属性:
Url.Action和Html.Action方法使用了ASP.NET MVC框架的映射引擎产生URL(Part 2中详细介绍了该部分内容).通过这种凡是,只要我们修改了站点的映射规则,我们不需要修改控制类或视图中的任何代码。例如,我们可以使用一个更加REST的路径,例如使用/Products/1/Edit取代/Products/Edit/1,控制类和视图不需要做任何修改仍可以正常工作。
编辑产品功能的实现(3-Update方法)
我们的最后一步是实现ProductsController中的Update方法:
在前面的Create方法中我们使用了UpdateForm扩展方法来自动从请求中获取产品对象,而在这里我们需要首先从数据库中获取原始的对象数据,然后将用户所做的修改应用到对象,并将修改保存到数据库中。
修改成功后,页面返回到产品列表也并自动将对产品所做的修改应用的对应得列表中。
总结
希望这篇文章对ASP.NET MVC框架对表单输入和调的处理进行了清晰的描述,同时提供了采用该方式针对普通数据项进行编辑和处理的例子。
点击这里下载包含该应用程序的代码文件。
后面的文章将介绍如何对表单输入和编辑应用进行数据验证和错误捕获。我将会介绍用于快速开发内置数据和安全支持,同时我们将讨论在MVC框架中如何使用ASP.NETAJAX来实现AJAX,并深入讨论单元测试的进行和对Controller依赖性注入(Dependencies injection)的操作。
希望对你有所帮助,
Scott