背景
我们一直在将大量遗留代码和系统迁移到 ASP.NET MVC 表单。我已经用 MVC 4 编写了一些 CRUD 类型的接口,使用模型绑定、验证、属性等,所以我对整个范例相当熟悉。到目前为止,所有这些表单都在我们的后端管理应用程序中,在这些应用程序中进行非常严格的输入验证是值得的。我们正在 MVC 中启动我们的第一个面向消费者的应用程序,并且面临着不同类型的问题。
问题
我们在这一领域的传统形式是我们公司的主要收入引擎。消费者体验的可用性是当今的规则。为此,我们希望我们的表单尽可能宽松——遗留系统做了很多事情来自动纠正用户输入(当然,每次都是完全自定义的、非标准的方式)。为此,我们不需要输入验证,而是需要卫生。
例子
我们要求用户输入隐含测量单位的数字输入。常见的是货币金额或平方英尺。输入标签很清楚他们不需要提供这些格式:
大约多少平方米?(例如:2000)
你的预算是多少?(例如:150)
人就是人,不是每个人都遵循指示,我们经常得到如下答案:
约 2100
1500 平方英尺
47.50 美元,给予或接受
(好吧,我夸大了最后一个。)我们最终存储到业务逻辑中的模型接受这些字段的数字输入类型(例如 int 和 float)。我们当然可以使用数据类型验证器属性(例如[DataType(DataType.Currency)]
预算输入,或者只是让字段类型为平方英尺的整数)来向用户清楚地表明他们做错了,提供有用的错误消息,例如:
平方英尺只能是数字。
然而,更好的用户体验是尝试尽可能从容地解释他们的回答,这样他们就可以在尽可能少的中断的情况下完成表单。(请注意,我们有一个广泛的客户服务部门,他们可以在之后解决我们系统上的错误,但我们必须让用户在我们联系之前填写表格。)对于上面的平方英尺示例,这只是意味着剥离非数字字符。对于预算,这意味着去掉所有不是数字或小数点的东西。只有这样我们才会应用其余的验证(是一个数字,大于 0,小于 50000 等)
我们被困在实现这一目标的最佳方法上。
潜在的解决方案
我们已经考虑了自定义属性、自定义模型绑定以及位于模型和数据库之间的单独的清理器服务类。以下是我们在尝试决定方法时考虑的一些因素。
自定义验证属性
我已经阅读了许多有用的资源。(它们具有不同程度的相关性和新近度。我发现很多东西都是为 MVC2 或 MVC3 编写的,并且在 MVC4 中具有标准属性。)
- 扩展 ASP.NET MVC 的验证
- ASP.NET MVC3 中的自定义验证属性
- 许多关于输入清理的问题和主题都集中在跨站点脚本攻击或数据库注入上。
我没有发现有人在做我想做的事,这将改变模型价值本身。我显然可以创建该值的本地副本,对其进行清理并提供通过/失败,但这会导致大量重复代码。在保存到数据库之前,我仍然需要再次清理任何输入值。
更改模型值本身有 3 个好处:
- 它会影响后续的验证规则,从而提高其接受率。
- 该值更接近将放入数据库的值,从而减少了存储前所需的额外准备和映射开销。
- 如果表单因其他原因被拒绝,它会温和地向用户建议“您正在努力处理这些字段”。
这是一种有效的方法吗?是否有人以我刚刚错过的这种方式使用了验证属性?
自定义模型绑定
我阅读了Splitting DateTime - Unit Testing ASP.NET MVC Custom Model Binders,它侧重于自定义日期时间输入字段,并在模型绑定层完成了自定义验证和解析。这与模型本身更接近,因此它似乎是修改模型值的更合适的地方。事实上,该示例class DateAndTimeModelBinder : IModelBinder
在某些地方确实做到了这一点。
但是,为本示例提供的控制器操作签名并未使用整体模型类。看起来像这样
public ActionResult Edit(int id,
[DateAndTime("year", "mo", "day", "hh","mm","secondsorhwatever")]
DateTime foo) {
而不是这个
public ActionResult Edit(
MyModelWithADateTimeProperty model) {
在那之前不久,文章确实说
一、用法。您可以通过在 Global.asax 中注册此自定义模型绑定器来管理您的所有日期时间:
ModelBinders.Binders[typeof(DateTime)] =
new DateAndTimeModelBinder() { Date = "Date", Time = "Time" };
这是否足以为单参数模型示例中的日期时间字段调用模型绑定MyModelWithADateTimeProperty
?
我在这里看到的另一个潜在缺点是模型绑定器对类型进行操作,而不是您可以应用于标准数据类型的属性。例如,我想应用的每组验证规则都需要一个新的自定义类型。这不一定是坏事,但它可能会变得混乱并导致大量重复代码。想象:
public class MyDataModel {
[Required]
public CurrencyType BudgetRange { get; set; }
public PositiveOnlyCurrencyType PaymentAmount { get; set; }
[Required]
public StripNonDigitsIntegerType SquareFootage { get; set; }
不是我见过的最丑陋的模型代码,但也不是最漂亮的。
自定义,外部洗涤器类
这对我来说问题最少,但它也有最多的缺点。我以前做过一些这样的事情,只是因为以下原因之一而真正后悔:
- 与控制器和模型分开,几乎不可能优雅地将其验证规则扩展到客户端。
- 它彻底混淆了不同模型字段的可接受输入和不可接受输入。
- 它创建了一些非常麻烦的箍来向用户显示错误。您必须将模型状态传递给清理器服务,这使您的清理器服务唯一地绑定到 MVC 框架。或者,您必须使您的清理器服务能够以控制器可以消化的格式返回错误,这比控制器通常推荐的逻辑要多。
问题
您将采取(或者,您已经采取)哪种方法来完成这种类型的消毒?它为您解决了哪些问题?你遇到了什么问题?