更正ASP.NET MVC4中的用户输入

时间:2013-09-21 23:21:06

标签: c# asp.net asp.net-mvc validation asp.net-mvc-4

背景

我们一直在迁移大量遗留代码&系统到ASP.NET MVC表单。我已经使用模型绑定,验证,属性等编写了许多与MVC 4的CRUD类型接口,因此我非常熟悉整个范例。到目前为止,所有这些形式一直在我们的后端管理和&管理应用程序,它需要非常严格的输入验证。我们正在MVC推出我们的第一个面向消费者的应用程序,并面临着不同类型的问题。

问题

我们在该领域的传统形式是我们公司的主要收入引擎。消费者体验的可用性是当今的规则。为此,我们希望我们的表单尽可能宽松 - 遗留系统做了很多事情来自动纠正用户输入(当然,每次都是完全自定义的,非标准的方式)。为此,我们不需要输入验证,因为我们需要卫生设施。

实施例

我们要求用户提供隐含的度量单位的数字输入。常见的是货币金额或平方英尺。输入标签很清楚,他们不需要提供这些格式:

  

近似面积是多少? (例如:2000)

     

您的预算是多少? (例如:150)

人是人,不是每个人都遵循指示,我们经常得到如下答案:

  

大约2100

     

1500平方英尺

     

$ 47.50,给予或接受

(好吧,我夸大了最后一个。)我们最终存储到业务逻辑中的模型接受这些字段的数字输入类型(例如int& float)。我们当然可以使用datatype validator attributes(示例[DataType(DataType.Currency)]作为预算输入,或者只是将字段类型设置为平方英尺的整数),以清楚地向用户表明他们做错了,提供了有用的帮助错误消息,例如:

  

平方英尺必须只是数字。

然而,更好的用户体验是尝试尽可能宽松地解释他们的响应,因此他们可以尽可能少地中断完成表单。 (注意我们有一个广泛的客户服务方面,他们可以在我们的系统之后解决错误,但我们必须让用户在我们联系之前填写表格。)对于上面的平方镜头示例,这只是意味着剥离非数字字符。对于预算,这意味着要删除所有不是数字或小数点的内容。只有这样我们才能应用剩余的验证(是一个数字,大于0,小于50000等)。

我们坚持采取最佳方法来实现这一目标。

潜在解决方案

我们考虑了自定义属性,自定义模型绑定以及在模型和数据库之间存在的单独的scrubber服务类。以下是我们在尝试确定方法时考虑的一些注意事项。

自定义验证属性

我已经阅读了很多有用的资源。 (它们具有不同程度的相关性和新近性。我找到的很多东西都是为MVC2或MVC3编写的,并且在MVC4中可以使用标准属性。)

我找不到的是有人在做我想做的事情,这会改变模型价值本身。我显然可以创建值的本地副本,清理它并提供通过/失败,但这会导致大量重复的代码。在保存到数据库之前,我仍然需要再次清理任何输入值。

更改模型值本身有3个好处:

  1. 它影响后续验证规则,这将提高其接受率。
  2. 该值更接近将放入数据库的值,从而减少了额外的准备工作。存储前需要的映射开销。
  3. 如果表单因其他原因被拒绝,则会向用户轻轻地建议"您正试图在这些字段上努力。"
  4. 这是一种有效的方法吗?是否有人以这种方式使用验证属性而我错过了?

    自定义模型绑定

    我阅读了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; }
    

    不是我见过的最丑陋的型号代码,但也不是最漂亮的。

    自定义,外部清理程序类

    这对我来说问题最少,但也有最大的缺点。我以前做过这样的事情,只是因为以下原因之一而后悔:

    1. 与控制器模型分开,几乎不可能优雅地将其验证规则扩展到客户端。
    2. 它彻底混淆了什么是什么,什么不是不同模型领域的可接受输入。
    3. 它创建了一些非常麻烦的箍,用于向用户显示错误。您必须将模型状态传递给scrubber服务,这使得您的scrubber服务与MVC框架唯一关联。或者你必须使你的擦洗服务能够以控制器可以消化的格式返回错误,这比通常建议用于控制器的逻辑更多。
    4. 问题

      您会采取哪种方法(或者,您已经采取过)来实现这种类型的消毒?它为你解决了什么问题?你遇到了什么问题?

1 个答案:

答案 0 :(得分:3)

我会采用ModelBinder方法。

当表单数据进入时 - 它会转到模型绑定器基础结构。在那里你可以覆盖十进制模型绑定器来优化输入。之后,您可以将其发送到验证例程,而无需编写特定的验证属性或类似的东西。

此外,您可以使用一个智能模型绑定器,它将执行internaly类型切换或覆盖ModelBinderProvider,因此您的代码不会使用ModelBinderAttribute膨胀。 Jimmy Bogart article就此而言。此外,您将获得一些灵活性,因为您可以使用属性来声明模型是否使用严格绑定或自适应绑定。

总体而言,恕我直言,验证属性不会改变输入。他们应该验证它。模型粘合剂实际上负责将所有奇怪的东西转换成系统中可用的东西,而第三种方法复制模型粘合剂功能)

希望这对我的英语有帮助和抱歉)