对我的ViewModel列表中的每个项目进行数据验证

时间:2014-04-28 09:19:35

标签: c# validation custom-attributes asp.net-mvc-viewmodel

要使用正则表达式进行验证,我通常会这样做:

// In my ViewModel
[RegularExpression("MyRegex", ErrorMessageResourceName = "MyErrorMessage")]
public string MyField { get; set; }

HTML帮助

@Html.TextBoxFor(model => model.MyField)

生成一个如下所示的标记:

<input type="text" class="valid" name="MyField" value="" id="MyField" data-val="true" data-val-regex-pattern="MyRegex" data-val-regex="MyErrorMessage"></input>

问题是我想拥有一个动态数量的字段,现在正在使用

// In my ViewModel
[RegularExpression("MyRegex", ErrorMessageResourceName = "MyErrorMessage")]
public IList<string> MyField { get; set; }

这一次

@Html.TextBoxFor(model => model.MyField[0])

将生成(没有正则表达式html属性)

<input id="MyField_0_" type="text" value="" name="MyField[0]"></input>

如何在我的ViewModel中绑定具有DataAnnotation验证属性的列表元素时,确保创建data-val html属性?

3 个答案:

答案 0 :(得分:7)

数据注释无法应用于列表元素。你需要做的是创建一个包装类并将数据注释应用于包装类中的元素,如下所示:

public IList<MyField> MyFields {get;set;}

public class MyField
{
    [RegularExpression("MyRegex", ErrorMessageResourceName = "MyErrorMessage")]
    public string Value
}

用法:

@Html.TextBoxFor(model => model.MyFields[0].Value)

答案 1 :(得分:3)

您正在使用DataAnnotations进行验证。据我所知,您正在寻找一种方法将DataAnnotation验证应用于列表的每个元素。

每当调用Html.EditorFor时,它都会获取已传递给它的模型的ModelMetadata,然后获取与该模型关联的任何ModelValidator。这些ModelValidator的存在导致HTML中的“data-val- *”属性。

当Html.EditorFor作为模型(或任何可枚举的)传递列表时,它首先获取ModelMetadata和属性的相关Validators - 在您的情况下,它将获取与“MyField”属性关联的ModelMetadata然后是验证器 - 在这种情况下为'RegularExpression'。它接下来遍历字符串列表并获取每个字符串的ModelMetadata和Validators。虽然已为每个字符串构造了ModelMetadata,但没有为这些字符串指定验证器。这是显示字符串但未将验证属性添加到HTML元素的原因。

我看到它的方式,你可以通过在运行时将'MyField'属性上指定的Validator添加到所有列表元素来实现。

这可以通过

完成
  1. 为所有收藏集编写共享编辑器模板
  2. 将当前ModelMetadataProvider设置为DataAnnotationsModelMetadataProvider
  3. 覆盖DataAnnotationsModelValidatorProvider'的'GetValidators'方法
  4. 步骤1的共享编辑器模板如下所示

    @model System.Collections.Generic.IEnumerable<object>
    @{
        ViewBag.Title = "Collection";
        var modelMetadata = this.ViewData.ModelMetadata;
        var validators = modelMetadata.GetValidators(ViewContext).ToList();
        ViewContext.HttpContext.Items["rootValidators"] = validators;
    }
    
    @foreach (var item in Model)
    {
        @Html.EditorFor(m => item)
    }
    

    您可以在上面的代码中看到我们正在获取列表中指定的所有验证器。稍后将将这些验证器添加到列表的元素中。它们已存储在HttpContext.Items中,以便在我们的自定义ModelValidatorProvider中使用。

    步骤2 - 在Global.asax中,输入以下代码 -

    ModelValidatorProviders.Providers.Clear();
    ModelValidatorProviders.Providers.Add(new DAModelValidatorProvider());
    
    ModelMetadataProviders.Current = new CachedDataAnnotationsModelMetadataProvider();
    

    步骤3 - 通过覆盖GetValidators方法编写自己的ModelValidatorProvider,如下面的代码所示

    public class DAModelValidatorProvider : DataAnnotationsModelValidatorProvider
    {
        protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
        {
            var validators = base.GetValidators(metadata, context, attributes).ToList();
    
            // get root validators of the collection. this was stored in the editor template - fetching it for use now.
            // fetching the rootvalidators inside this method is a bad idea because we have to call GetValidators method on the 
            // containers ModelMetadata and it will result in a non-terminal recursion
            var rootValidators = context.HttpContext.Items["rootValidators"] as IEnumerable<ModelValidator>;
            if (rootValidators != null)
            {
                foreach (var rootValidator in rootValidators)
                {
                    validators.Add(rootValidator);
                }
            }
    
            return validators;
        }
    }
    

    执行上述3个步骤对我有用。但是,我使用了Html.EditorFor而不是Html.TextBoxFor。使用Html.EditorFor,我的方式没有给我正确的id和名称属性 - 我认为这在事物计划中是一个微不足道的问题。我已经为此创建了一个解决方案并将其上传到https://github.com/swazza85/Stackoverflow,因此您可以试一试,看看它是否符合您的需求。我在这里所做的并不是一个完整的解决方案,但希望它可以让你无需改变你的模型。

    干杯, Swarup。

答案 2 :(得分:2)

我使用@ swazza85答案,但必须根据我的情况修改它。希望如果其他人使用他的解决方案,他们可以从我的修改中受益。我必须将IEnumerable<object>更改为IList<object>(或者在我的情况下IList<decimal?>,因为IList<object>会引发错误。)。然后我不得不使用for迭代器,因为单词item被添加到name属性,而模型绑定器没有将这些项绑定到我的模型。

@model System.Collections.Generic.IList<decimal?>

@{
    ViewBag.Title = "Collection";
    var modelMetadata = this.ViewData.ModelMetadata;
    var validators = modelMetadata.GetValidators(ViewContext).ToList();
    ViewContext.HttpContext.Items["rootValidators"] = validators;
}

@for (var i = 0; i < Model.Count(); i++)
{
    @Html.EditorFor(model => Model[i], new { htmlAttributes = new { @class = "form-control" } })
    @Html.ValidationMessageFor(model => Model[i], "", new { @class = "text-danger" })
}

此外,如果您不想在Global.asax文件中清除提供程序,只需在if语句中返回验证程序并在其外部返回一个空列表,请注意此编辑器模板必须在您的最后一个视图或它将遇到其他属性或模板的问题。您可以在模板的末尾设置ViewContext.HttpContext.Items["rootValidators"] = null

  protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
    {
      var validators = base.GetValidators(metadata, context, attributes).ToList();

      // get root validators of the collection. this was stored in the editor template - fetching it for use now.
      // fetching the rootvalidators inside this method is a bad idea because we have to call GetValidators method on the 
      // containers ModelMetadata and it will result in a non-terminal recursion
      var rootValidators = context.HttpContext.Items["rootValidators"] as IEnumerable<ModelValidator>;

      if (rootValidators != null)
      {
        foreach (var rootValidator in rootValidators)
        {
          validators.Add(rootValidator);
        }
        return validators;
      }

      return new List<ModelValidator>();
    }