【问题标题】:MVC 3 Complicated validation of modelsMVC 3 复杂的模型验证
【发布时间】:2011-11-02 10:51:11
【问题描述】:

当前用于 MVC 3 的验证方法似乎是 ValidationAttributes。我有一个非常特定于该模型的类验证,并且在几个属性之间有交互。

基本上,该模型包含其他模型的集合,并且它们都以相同的形式进行编辑。我们称它为 ModelA,它有一个 ModelB 的集合。我可能需要验证的一件事是 ModelB 的某些属性的总和小于 ModelA 的属性。用户可以在一些选项中分配 X 个点数。

ValidationAttributes 非常通用,我不确定它们是否适合这项工作。

我不知道 MVC 3 如何支持 IDateErrorInfo 以及它是否可以直接使用。

一种方法是通过方法进行验证,但这意味着我无法进行客户端验证。

做这样的事情的正确方法是什么?我还有其他选择吗?我是不是低估了 ValidationAttribute 的威力?

【问题讨论】:

    标签: asp.net-mvc-3 validation idataerrorinfo


    【解决方案1】:

    IDateErrorInfo

    IDateErrorInfo 由 MVC 框架支持(可以找到 Microsoft 教程here)。默认模型绑定器将负责通过将 html 表单元素绑定到模型来重新创建模型对象。如果模型绑定器检测到模型实现了接口,那么它将使用接口方法来验证模型中的每个属性或验证整个模型。有关更多信息,请参阅教程。

    如果您想使用此方法使用客户端验证,那么(引用 Steve Sanderson 的话)“利用其他验证规则的最直接方法是在视图中手动生成所需的属性”:

    <p>
    @Html.TextBoxFor(m.ClientName, new { data_val = "true", data_val_email = "Enter a valid email address", data_val_required = "Please enter your name"})
    
    @Html.ValidationMessageFor(m => m.ClientName)
    </p>
    

    这可用于触发已定义的任何客户端验证。有关如何定义客户端验证的示例,请参见下文。

    显式验证

    正如您所提到的,您可以在操作中明确验证模型。例如:

    public ViewResult Register(MyModel theModel)
    {
        if (theModel.PropertyB < theModel.PropertyA)
            ModelState.AddModelError("", "PropertyA must not be less then PropertyB");
    
        if (ModelState.IsValid)
        {
            //save values
            //go to next page
        }
        else
        {
            return View();
        }
    }
    

    然后您需要在视图中使用@Html.ValidationSummary 来显示错误消息,因为上面的代码会添加模型级别错误而不是属性级别错误。

    要指定属性级别的错误,您可以编写:

    ModelState.AddModelError("PropertyA", "PropertyA must not be less then PropertyB");
    

    然后在视图中使用:

    @Html.ValidationMessageFor(m => m.PropertyA);
    

    显示错误信息。

    同样,任何客户端验证都需要通过定义属性手动链接到视图中的客户端验证。

    自定义模型验证属性

    如果我正确理解了这个问题,您正在尝试验证一个模型,该模型包含一个值和一个集合,其中集合上的一个属性将被求和。

    对于我将给出的示例,视图将向用户显示一个最大值字段和 5 个值字段。最大值字段将是模型中的单个值,其中 5 个值字段将成为集合的一部分。验证将确保值字段的总和不大于最大值字段。验证将被定义为模型上的一个属性,该属性也将很好地链接到 javascript 客户端验证。

    观点:

    @model MvcApplication1.Models.ValueModel
    
    <h2>Person Ages</h2>
    
    @using (@Html.BeginForm())
    {
        <p>Please enter the maximum total that will be allowed for all values</p>
        @Html.EditorFor(m => m.MaximumTotalValueAllowed)
        @Html.ValidationMessageFor(m => m.MaximumTotalValueAllowed)
    
        int numberOfValues = 5;
    
        <p>Please enter @numberOfValues different values.</p>
    
        for (int i=0; i<numberOfValues; i++)
        {
            <p>@Html.EditorFor(m => m.Values[i])</p>
        }
    
        <input type="submit" value="submit"/>
    }
    

    我没有对值字段添加任何验证,因为我不想使示例过于复杂。

    模型:

    public class ValueModel
    {
        [Required(ErrorMessage="Please enter the maximum total value")]
        [Numeric] //using DataAnnotationExtensions
        [ValuesMustNotExceedTotal]
        public string MaximumTotalValueAllowed { get; set; }
    
        public List<string> Values { get; set; }
    }
    

    行动:

    public ActionResult Index()
    {
        return View();
    }
    
    [HttpPost]
    public ActionResult Index(ValueModel model)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }
        else
        {
            return RedirectToAction("complete"); //or whatever action you wish to define.
        }
    }
    

    自定义属性:

    模型上定义的[ValuesMustNotExceedTotal]属性可以通过重写ValidationAttribute类来定义:

    public class ValuesMustNotExceedTotalAttribute : ValidationAttribute
    {
        private int maxTotalValueAllowed;
        private int valueTotal;
    
        public ValuesMustNotExceedTotalAttribute()
        {
            ErrorMessage = "The total of all values ({0}) is greater than the maximum value of {1}";
        }
    
        public override string FormatErrorMessage(string name)
        {
            return string.Format(ErrorMessageString, valueTotal, maxTotalValueAllowed);
        }
    
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            PropertyInfo maxTotalValueAllowedInfo = validationContext.ObjectType.GetProperty("MaximumTotalValueAllowed");
            PropertyInfo valuesInfo = validationContext.ObjectType.GetProperty("Values");
    
            if (maxTotalValueAllowedInfo == null || valuesInfo == null)
            {
                return new ValidationResult("MaximumTotalValueAllowed or Values is undefined in the model.");
            }
    
            var maxTotalValueAllowedPropertyValue = maxTotalValueAllowedInfo.GetValue(validationContext.ObjectInstance, null);
            var valuesPropertyValue = valuesInfo.GetValue(validationContext.ObjectInstance, null);
    
            if (maxTotalValueAllowedPropertyValue != null && valuesPropertyValue != null)
            {
                bool maxTotalValueParsed = Int32.TryParse(maxTotalValueAllowedPropertyValue.ToString(), out maxTotalValueAllowed);
    
                int dummyValue;
                valueTotal = ((List<string>)valuesPropertyValue).Sum(x => Int32.TryParse(x, out dummyValue) ? Int32.Parse(x) : 0);
    
                if (maxTotalValueParsed && valueTotal > maxTotalValueAllowed)
                {
                    return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
                }
            }
    
            //if the maximum value is not supplied or could not be parsed then we still return that the validation was successful.
            //why?  because this attribute is only responsible for validating that the total of the values is less than the maximum.
            //we use a [Required] attribute on the model to ensure that the field is required and a [Numeric] attribute
            //on the model to ensure that the fields are input as numeric (supplying appropriate error messages for each).
            return null;
        }
    }
    

    为自定义属性添加客户端验证:

    要向该属性添加客户端验证,需要实现 IClientValidatable 接口:

    public class ValuesMustNotExceedTotalAttribute : ValidationAttribute, IClientValidatable
    {
    //...code as above...
    
        //this will be called when creating the form html to set the correct property values for the form elements
        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule {
                ValidationType = "valuesmustnotexceedtotal", //the name of the client side javascript validation (must be lowercase)
                ErrorMessage = "The total of all values is greater than the maximum value." //I have provided an alternative error message as i'm not sure how you would alter the {0} and {1} in javascript.
            };
    
            yield return rule;
            //note: if you set the validation type above to "required" or "email" then it would use the default javascript routines (by those names) to validate client side rather than the one we define
        }
    }
    

    如果您此时要运行应用程序并查看定义属性的字段的源 html,您将看到以下内容:

    <input class="text-box single-line" data-val="true" data-val-number="The MaximumTotalValueAllowed field is not a valid number." data-val-required="Please enter the maximum total value" data-val-valuesmustnotexceedtotal="The total of all values is greater than the maximum value." id="MaximumTotalValueAllowed" name="MaximumTotalValueAllowed" type="text" value="" />
    

    特别注意data-val-valuesmustnotexceedtotal 的验证属性。这就是我们的客户端验证链接到验证属性的方式。

    添加客户端验证:

    要添加客户端验证,您需要在视图的标记中添加以下类似的库引用:

    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
    

    您还需要确保在 web.config 中打开客户端验证,尽管我认为这应该默认打开:

    <add key="ClientValidationEnabled" value="true"/>
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
    

    剩下的就是在视图中定义客户端验证。请注意,此处添加的验证是在视图中定义的,但如果它是在库中定义的,则可以将自定义属性(可能不是这个)添加到其他视图的其他模型中:

    <script type="text/javascript">
    
        jQuery.validator.unobtrusive.adapters.add('valuesmustnotexceedtotal', [], function (options) {
            options.rules['valuesmustnotexceedtotal'] = '';
            options.messages['valuesmustnotexceedtotal'] = options.message;
        });
    
        //note: this will only be fired when the user leaves the maximum value field or when the user clicks the submit button.
        //i'm not sure how you would trigger the validation to fire if the user leaves the value fields although i'm sure its possible.
        jQuery.validator.addMethod('valuesmustnotexceedtotal', function (value, element, params) {
    
            sumValues = 0;
    
            //determine if any of the value fields are present and calculate the sum of the fields
            for (i = 0; i <= 4; i++) {
    
                fieldValue = parseInt($('#Values_' + i + '_').val());
    
                if (!isNaN(fieldValue)) {
                    sumValues = sumValues + fieldValue;
                    valueFound = true;
                }
            }
    
            maximumValue = parseInt(value);
    
            //(if value has been supplied and is numeric) and (any of the fields are present and are numeric)
            if (!isNaN(maximumValue) && valueFound) {
    
                //perform validation
    
                if (sumValues > maximumValue) 
                {
                    return false;
                }
            }
    
            return true;
        }, '');
    
    </script>
    

    应该就是这样。我确信可以在这里和那里进行改进,如果我稍微误解了这个问题,你应该能够根据你的需要调整验证。但我相信这种验证似乎是大多数开发人员编写自定义属性的方式,包括更复杂的客户端验证。

    希望这会有所帮助。如果您对上述内容有任何问题或建议,请告诉我。

    【讨论】:

    • 显式可能不支持客户端验证。然而,定制的相当不错,但我仍然认为解决方案对于特定问题来说有点过于笼统。
    • @Ingo Vals - 我想我之前的回答误解了你的问题。我已经更新了答案,希望它是一个更有用的解决方案。希望对您有所帮助。
    【解决方案2】:

    【讨论】:

    • 链接示例不适用于客户端验证问题,因为它没有实现IClientValidatable
    【解决方案3】:

    我也遇到过类似的情况,需要比较属性A和属性B的值,我通过以下方式完成:

     public sealed class PropertyAAttribute : ValidationAttribute
    {
        public string propertyBProperty { get; set; }
        // Override the isValid function
        public override bool IsValid(object value)
        {
             // Do your comparison here, eg:
              return A >= B;
        }
    }
    

    然后像这样使用自定义验证属性:

     [PropertyA(propertyBProperty = "PropertyB")]
     public string Property A {get; set;}
    

    我也很努力地从别人那里得到了这个解决方案,希望能有所帮助!

    【讨论】:

    • 虽然这可行,但我不喜欢弱类型,将属性作为字符串解决方案传递。我开始认为目前还没有真正的解决方案。
    【解决方案4】:

    您的模型类可以实现IValidatableObject 接口。

    这样您就可以访问模型类的所有属性,并且可以执行所有自定义验证。

    您还拥有IClientValidatable 接口,用于客户端验证,但我不确定通过直接在模型类中实现它是否会由 MVC 选择客户端验证,因为我只使用此接口来指定客户端验证在自定义验证属性中。

    【讨论】:

    • 与 ValidationAttribute 相比,它的工作原理如何? ModelState.IsValid 的工作方式是否相同?
    • IValidatableObject被MVC自动拾取,结果会反映在ModelState.IsValid中。关于客户端验证的接口我不确定,因为从未在模型类中直接使用过。
    • 我现在正在检查 Knockout.js 以及客户端方面的内容,所以我也必须看看有什么用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-24
    • 2011-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多