上个星期四,ASP.NET MVC开发团队发布了ASP.NET MVC框架的“第五个预览版”。你可以在这里下载这个新版本。这“第五个预览版” 可在.NET 3.5和最新发布的.NET 3.5 SP1下工作,也可在Visual Studio 2008以及免费的Visual Web Developer 2008 Express SP1版本(现在支持类库项目和web应用项目了)下使用。
第五个预览版包含了建立在“第四个预览版”的新功能之上的一堆新功能和改进。你可以在这里阅读“第五个预览版”的发布说明,其中总结了所做变动和新添特性。在这个博客贴子中,我将讨论这个版本中一个最大的关注点:表单提交场景。你可以在这里下载我将在下面建造的一个应用的完整版本。
让我们看一个简单的表单提交场景,往产品数据库中加一个新产品:
上面的页面是在用户访问我们应用的“/Products/Create”URL时返回的,该网页的的HTML表单标识如下:
上面的标识是标准的HTML,在
单元测试和UpdateModel
在这个星期的第五个预览版中,UpdateModel方法总是从Request对象的Form提交集合中取得数值的,这意味着,要测试上面的表单提交action方法,你需要在你的单元测试里模拟Request对象。
在下一个MVC版本中,我们还将加一个重载的UpdateModel方法,它将允许你传入一个你自己的数值集合为它所用。例如,我们将能够使用第五个预览版中的新的FormCollection类型(有一个ModelBuilder对象会自动地使用所有的表单提交值来填充它),将它作为参数传给UpdateModel方法,象这样:
使用象上面这样的方法将允许我们不用使用任何模拟,就可以单元测试我们的表单提交场景。下面是我们可以编写的一个单元测试的例程,用来测试一个POST场景用新的数值成功地更新,然后重新定向到我们action方法的GET版本。 注意,为单元测试我们的controller的所有功能,我们不需要模拟任何东西(也不依赖任何特别的辅助方法):
在处理表单提交场景时,一个需要注意的重要事情是处理出错条件。这些情形包括,用户提交了错误的输入(例如,对类型是Decimal的单价,输入了字符串,而不是数字),以及输入格式是合法的,但应用后面的业务规则不允许某些东西得到创建/更新/保存(例如,对停卖了的产品的新的订购)。
如果用户在填表时犯了错,表单应该重新显示,并输出清楚明确的错误信息,引导用户修正输入。表单还应该保留用户当初输入的数据,这样,他们就不用手工重填了。这个过程应该重复多次,直到表单成功完成为止。
ASP.NET MVC中的基本的表单错误处理和输入验证
在上面的产品编辑例程中,我们在Controller或View中都没怎么编写错误处理代码。我们的Edit提交action方法只简单地用了一个try/catch错误处理块将UpdateModel()输入映射调用以及数据库保存SubmitChanges()调用围了起来。如果错误发生的话,controller将一个输出消息保存到TempData集合中,然后返回/重新显示我们的编辑视图:
在ASP.NET MVC以前的版本中,上面的代码提供不了很好的用户体验(因为有错的话,它不会高亮显示问题所在,也不会保留用户输入)。
但在第五个预览版中,你会发现,只用上面的代码,出错时,你现在能得到一个不错的用户体验。特别地,你会发现,当我们的编辑视图因为输入错误重新显示时,它会高亮显示所有有问题的输入控件,同时保留它们的输入值,让我们来修正:
你也许会问,单价文本框是如何用红色高亮显示自己,知道输出原先输入的用户值的呢?
第五个预览版引进了一个新的“ModelState”集合,该集合是在显示时,作为ViewData的一部分,从Controller传递给View的。 ModelState集合给Controllers提供了一个方式,来表示传给View的模型对象或模型属性中有错存在,允许指定一个描述问题所在的对人类友好的错误消息,以及由用户输入的原始数据。
开发人员可以在Controller action方法中,明确地编写代码往ModelState集合中添加个项。 ASP.NET MVC 的模型绑定器和UpdateModel()辅助方法也会在遇上输入错误时自动地默认填充这个集合。因为在上面的Edit action方法中,我们使用了UpdateModel()辅助方法,在它试图将单价文本框的“gfgff23.02”输入映射到Product.UnitPrice(类型是Decimal)属性时失败了的时候,它会自动地往ModelState集合中加一项。
View中的Html辅助方法在默认情形下,在显示输出时,现在会查看ModelState集合。如果它们正显示的一项有错存在的话,它们现在会将原先输入的用户数据显示出来,同时会将一个CSS错误类显示到HTML输入控件元素上去。例如,在上面的“Edit”视图中,我们使用了 Html.TextBox() 辅助方法来显示我们Product对象的UnitPrice:
在上面的出错场景下,视图被显示时,Html.TextBox()会查看ViewData.ModelState集合,看我们的Product对象的“UnitPrice”属性是否有问题,当它看到有问题时,它会将原先输入的用户数据"gfgff23.02"输出,同时将一个css类加到它输出的上去:
你可以将出错css类的外观定制成你想要的任何外观,在新的ASP.NET MVC项目中创建的样式表中输入控件元素默认的出错CSS规则如下所示:
添加额外的验证消息
内置的HTML表单辅助方法对有问题的输入域提供了基本的视觉识别外观。现在让我们来往页面上再加一些更具描述性的出错信息,具体做法是,我们可以使用第五个预览版中这个新的Html.ValidationMessage()辅助方法,这个方法会输出与一个给定Model或Model属性相关联的ModelState集合中的错误信息。
例如,我们可以更新我们的视图,在文本框的右方使用Html.ValidationMessage()辅助方法,象这样
当页面因错而显示时,错误消息就会显示在有问题的控件域之后:
Html.ValidationMessage() 方法还有一个重载的版本,接受第二个参数,允许视图指定要显示的替换文字:
常见的一个用例是,在输入控件域后面输出 * 字符,然后将新的Html.ValidationSummary()辅助方法(也是第五个预览版中新加的)加到页面的顶部,来列出所有的错误消息:
Html.ValidationSummary()辅助方法然后就会用
注意,我们完全不用改动ProductsController类就取得了如此效果。
支持业务规则的验证
象上面那样支持输入验证场景是很有用的,但对大多数应用来说是不够的。在大多数场景下,你还想要能够执行业务规则,让你应用的用户界面能与它们干净地结合起来。
ASP.NET MVC支持任何的数据层抽象(无论是基于ORM与否),允许你对你的领域模型,以及相关联的规则/限制进行任意的结构化。象模型绑定器, UpdateModel辅助方法,以及所有的错误显示和验证消息支持这样的功能,都是特意设计的,这样你可以在你的MVC应用中使用你想要的任何首选数据访问故事(包括LINQ to SQL, LINQ to Entities, NHibernate, SubSonic, CSLA.NET, LLBLGen Pro, WilsonORMapper, DataSets, ActiveRecord等等)。
往LINQ to SQL实体中添加业务规则
在上面的例子中,我一直在使用LINQ to SQL 来定义我的Product实体和进行数据访问操作。迄今为止,在我的Product实体上使用的唯一领域规则/验证是那些由LINQ to SQL从SQL Server元数据(可空性,数据类型和长度等等)中推断出来的那些规则,这会捉住象上面那样的场景(我们试图给一个Decimal赋一个有问题的输入)。但是,它们无法构型那些使用SQL元数据难以声明的业务问题。例如,在一个产品停售之后,不能允许它的reorder(重订购)水平大于0,或者不允许一个产品以小于供应商的价格出售,等等。对这样的场景,我们需要在我们的模型中添加代码来表达和结合这些业务规则。
添加这些业务规则逻辑的错误地方是应用的UI层,在那里加这些规则不好,有很多理由。特别值得一提的是,几乎肯定会导致代码的重复,因为你最终会将这些规则从一个UI拷贝到另一个UI,从一个表单到另一个表单。除了费时外,这么做,在你改变业务规则逻辑时,如果你忘了在所有的地方做更新的话,有很大的几率会导致缺陷。
一个融合这些业务规则的好得多的地方是你的模型或领域层,那么做的话,不管是什么类型的用户界面或表单或服务,这些规则都可以被使用和执行。对规则的变动只要做一次,不用重复任何逻辑就可在任何地方使用。
我们有几个模式和方法可用,来将丰富的业务规则集成到上面我们一直在用的Product模型对象中去:我们可以在对象中定义这些规则,也可在对象外面定义这些规则。我们可以使用声明式规则,一个可重用的规则引擎框架,或者命令式代码(imperative code)。要点是,ASP.NET MVC允许我们使用任何一种方法或者所有这些方法(没有一堆功能要求你总是采用一种方式做事,你拥有对它们进行变通的灵活性,MVC功能的扩展性之强大,几乎可以与任何东西相结合)。
在本贴子中,我将使用一个比较简单的规则的方法。首先,我将象下面这样定义一个“RuleViolation”类,我们可以用它来捕捉住我们模型中正被违反的业务规则的信息。这个类会呈现一个ErrorMessage字符串属性,内含错误的细节,以及呈现与之相关联的造成规则违反的关键属性名称和属性值:
(注:为简单起见,我将只储存一个属性, 在更复杂的应用中,这也许会是一个列表,这样就可以指定多个属性。)
然后我将定义一个IRuleEntity接口,内含单个方法, GetRuleViolations(),将返回实体中所有业务规则违反的列表:
然后我可以让我的Product类实现这个接口,为保持这个例子简单起见,我将把规则定义和估算逻辑内嵌在方法之中。有更好的模式你可以用来促成可重用的规则,以及处理更复杂的规则。如果这个例程变大的话,我需要重构这个方法,这样,规则以及它们的估算将被定义在其它地方,但目前为保持简明,我们将象下面这样估算三个业务规则:
我们的应用现在可以查询Product(或任何其他IRuleEntity)实例来检查它目前的验证状态,以及取回RuleViolation对象,这些对象可用来帮助显示一个可以引导应用的用户修正这些违反的用户界面。它还允许我们独立于应用的用户界面来轻松地单元测试我们的业务规则。
在这个特定的例子里,我将选择确保,我们的Product对象在不合法的状态下是不能保存到数据库中去的(意味着在Product对象可保存到数据库之前,所有的RuleViolations都必须被修正)。在LINQ to SQL中,我们可以这么做,在Product部分类中,加一个OnValidate部分方法。这个方法会在数据库持久化时,被LINQ to SQL自动调用。在下面我将调用我们在上面添加的GetRuleViolations()方法,如果有未解决的错误,我将抛出一个异常。这会中止事务,防止数据库被更新:
那样,除了有个友好的辅助方法允许我们从一个Product中取出RuleViolations外,我们还确保在数据库被更新之前那些RuleViolations必须被修正。
将上述规则集成进ASP.NET MVC用户界面中
一旦象上面那样实现了业务规则,呈示RuleViolations后,将其集成进我们的ASP.NET MVC例程则是比较容易的事情。
因为我们往Product类中加了OnValidate部分方法,调用northwind.SubmitChanges()会抛出一个异常,如果我们试图保存的Product对象中有任何业余规则验证问题的话。这个异常会终止任何数据库事务,在下面的catch块中被捕捉住:
我们要往我们的错误catch块中加的的一行额外代码是调用定义在下面的UpdateModelStateWithViolations()辅助方法的逻辑。这个方法从一个实体中取出所有的规则违反的列表,然后将适当的模型错误(包括对实体对象上导致错误的属性的引用)更新ModelState集合:
完成之后,我们可以重新运行我们的应用。现在,除了看到与输入格式相关的错误信息外,ASP.NET MVC的验证辅助方法还将显示我们的业务规则的违反情形。
例如,我们可以将单价设成小于$1,将Reorder水平设成-1(从输入格式的角度来看,这2个值都是合法的,但两者都违反了业务规则)。当我们这么做,点击保存按钮时,我们将看到Html.ValidationSummary()列表中的错误信息,以及相关的文本框被特别标出了:
我们的业务规则可以跨越多个Product属性,例如,你也许注意到了,我在上面加了一条规则说,如果产品被中止的话,reorder水平不能大于0:
在这整个业务规则过程中,对我们的“Edit”视图模板要做的唯一变动是往文件中再加2个Product属性(Reorder和Discontinued):
现在我们可以往我们的Product实体中添加任何数目的额外业务验证规则,而且为使得界面支持它们,我们不需要更新Edit视图,也不要更新ProductsController。
我们还可以独立于我们的Controller和View,对我们的模型和业务规则进行单独的单元测试。我们也可以独立于我们的Controller, Views 和 Models对URL路径选择规则进行单独的单元测试。我们可以独立于我们的Views单独单元测试我们的Controller。本博客贴子展示的所有场景都不要求使用任何模拟(mocking)或替补(stubbing)就支持单元测试,最终的结果是极其容易测试的应用,而且支持很好的TDD工作流程。
本贴子对表单提交场景在ASP.NET MVC第五个预览版中的工作原理做了一个简单的介绍,希望在阅读之后,你对如何使用MVC模型处理表单和输入场景有了更好的认识,你可以在这里下载我在上面建造的应用的完整C#版本。本星期稍后,我将发布相应的VB版本(不幸的是,在我键入该文字的时候,已经是临晨4:30了,我需要在几个短短的小时后跳上一架飞机,但现在还没有开始收拾行装呢)。
重要事项: 如果你不喜欢MVC模型,或者发现它与你的开发风格相违的话,你不一定要用它。这产品完全是个可选项,它并不替代现有的WebForms模型。WebForms和MVC模型以后都会被同时支持和增强(下一个ASP.NET WebForms版本将增加更丰富的URL路由特性,更好的HTML标识/客户端ID/CSS支持等等)。所以,如果在阅读了上面的贴子之后,你认为“呃,我感觉这不是很自然”,那么不用担心,也不用觉得你应该或者需要使用它,你不用那么想。
在下一个MVC贴子里,我将讨论如何将AJAX集成进你的ASP.NET MVC应用。
希望本文对你有所帮助,
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/12639172/viewspace-621004/ ,如需转载,请注明出处,否则将追究法律责任。