自定义 DataAnnotationsModelMetadataProvider 不起作用 [英] Customized DataAnnotationsModelMetadataProvider not working

查看:29
本文介绍了自定义 DataAnnotationsModelMetadataProvider 不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有许多属性需要 1 个或多个验证属性,如下所示:

公共类TestModel{[一些][字符串长度(6)][CustomRequired]//更多属性...公共字符串卡车{获取;放;}}

请注意以上所有注释都有效.

我不想一直这样写,因为每当应用 Some 时,所有其他属性也会应用到该属性.我希望能够做到这一点:

公共类TestModel{[一些]公共字符串卡车{获取;放;}}

现在可以通过继承来实现;因此,我编写了一个自定义 DataAnnotationsModelMetadataProvider 并覆盖了 CreateMetadata.这会寻找任何用 Some 修饰的东西,然后给它添加更多的属性:

公共类 TruckNumberMetadataProvider : DataAnnotationsModelMetadataProvider{受保护的覆盖 ModelMetadata CreateMetadata(IEnumerable 属性,Type containerType,Func modelAccessor,Type modelType,string propertyName){var attributeList = attributes.ToList();if (attributeList.OfType().Any()){attributeList.Add(new StringLengthAttribute(6));attributeList.Add(new CustomRequiredAttribute());}返回 base.CreateMetadata(attributeList, containerType, modelAccessor, modelType, propertyName);}}

这些是万一有帮助的属性:

公共类 CustomRequiredAttribute : RequiredAttribute{公共 CustomRequiredAttribute(){this.ErrorMessage = "必填";}}公共类 SomeAttribute :RegularExpressionAttribute{公共 SomeAttribute(): base(@"^[1-9]d{0,5}$"){}}

使用

@Html.TextBoxFor(x => x.Truck)

HTML 渲染:

</输入>

问题/疑问

  1. 应用了 CustomRequired.但是,如果我使用 CustomRequired,它为什么会从基类 RequiredAttribute 中获取消息.data-val-required 应该只是说Required.
  2. 不应用 6 个字符的 StringLenth.StringLength 没有任何渲染的迹象,为什么?

解决方案

您的自定义 DataAnnotationsModelMetadataProvider 所做的是创建/修改与您的属性关联的 ModelMetada.>

如果您检查 ModelMetadata 类你会注意到它包含诸如string DisplayNamestring DisplayFormatString 等属性,这些属性是根据[Display][DisplayFormat] 属性.它还包含一个 bool IsRequired 属性,用于确定是否需要属性的值(稍后会详细介绍).

它不包含任何与正则表达式或最大长度相关的内容,也不包含任何与验证相关的内容(除了 IsRequired 属性和用于验证该值是否可以转换为 type).

HtmlHelper 方法负责生成传递给视图的 html.要生成 data-val-* 属性,您的 TextBoxFor() 方法在内部调用 HtmlHelperGetUnobtrusiveValidationAttributes() 方法/code> 类依次调用 DataAnnotationsModelValidatorProvider 类中的方法,该类最终生成 data-val 属性名称和值的 Dictionary生成html.

如果您想了解更多详细信息(请参阅下面的链接),我会将其留给您去探索源代码,但总而言之,它会获取应用于您的 Truck 属性的所有属性的集合,这些属性继承从 ValidationAttribute 构建字典.在您的情况下,唯一的 ValidationAttribute[Some] 它派生自 RegularExpressionAttribute 所以 data-val-regexdata-val-regex-pattern 属性被添加.

但是因为您已经在 TruckNumberMetadataProvider 中添加了 CustomRequiredAttributeModelMetadataIsRequired 属性已经被设置为 true.如果您检查 DataAnnotationsModelValidatorProviderGetValidators(),您将看到 RequiredAttribute 自动添加到属性集合中,因为您尚未应用到物业.相关的代码片段是

if (AddImplicitRequiredAttributeForValueTypes && metadata.IsRequired && !attributes.Any(a => a is RequiredAttribute)){attributes = attributes.Concat(new[] { new RequiredAttribute() });}

这导致 data-val-required 属性被添加到 html(它使用默认消息,因为它对你的 CustomRequiredAttribute 一无所知)

如果您想了解内部工作原理,可帮助您入门的源代码文件

  1. HtmlHelper.cs- 参考第 413 行的 GetUnobtrusiveValidationAttributes() 方法
  2. ModelValidatorProviders.cs- 获取用于验证的各种 ValidatorProviders
  3. DataAnnotationsModelValidatorProvider.cs- ValidationAttributes
  4. 的 ValidatorProvider

如果您真的只想使用一个 ValidationAttribute,一种可能的解决方案是让它实现 IClientValidatable 并在 GetClientValidationRules() 中实现方法,添加规则,例如

var rule = new ModelClientValidationRule{ValidationType = "必填",错误信息 = "必需"}

将由 ClientDataTypeModelValidatorProvider 读取(并删除您的 TruckNumberMetadataProvider 类).但是,这会造成维护噩梦,因此我建议您只需将 3 个验证属性添加到您的属性中

I have many properties that require 1 or more validation attributes, like the following:

public class TestModel
{
    [Some]
    [StringLength(6)]
    [CustomRequired] // more attributes...
    public string Truck { get; set; }
}

Please note all the above annotations work.

I do not want to write that all the time because whenever Some is applied, all other attributes are also applied to the property. I want to be able to do this:

public class TestModel
{
    [Some]
    public string Truck { get; set; }
}

Now this is doable by inheriting; therefore, I wrote a custom DataAnnotationsModelMetadataProvider and overrode the CreateMetadata. This looks for anything that is decorated with Some and then adds more attributes to it:

public class TruckNumberMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        var attributeList = attributes.ToList();
        if (attributeList.OfType<SomeAttribute>().Any())
        {
            attributeList.Add(new StringLengthAttribute(6));
            attributeList.Add(new CustomRequiredAttribute());
        }

        return base.CreateMetadata(attributeList, containerType, modelAccessor, modelType, propertyName);
    }
}

These are the attributes in case it helps:

public class CustomRequiredAttribute : RequiredAttribute
{
    public CustomRequiredAttribute()
    {
        this.ErrorMessage = "Required";
    }
}
public class SomeAttribute : RegularExpressionAttribute
{
    public SomeAttribute()
        : base(@"^[1-9]d{0,5}$")
    {
    }
}

Usage

@Html.TextBoxFor(x => x.Truck)

HTML Rendered:

<input name="Truck" id="Truck" type="text" value="" 
    data-val-required="The Truck field is required." 
    data-val-regex-pattern="^[1-9]d{0,5}$" 
    data-val-regex="The field Truck must match the regular expression '^[1-9]d{0,5}$'." 
    data-val="true">
</input>

Problems/Questions

  1. The CustomRequired was applied. But why does it pick up the message from the base class RequiredAttribute if I am using CustomRequired. The data-val-required should just say Required.
  2. The StringLenth of 6 chars is not applied. There is no sign of anything rendered for StringLength, why?

解决方案

What your custom DataAnnotationsModelMetadataProvider is doing is creating/modifying the ModelMetada associated with your property.

If you inspect the ModelMetadata class you will note that it contains properties such as string DisplayName and string DisplayFormatString which are set based on the application of the [Display] and [DisplayFormat] attributes. It also contains a bool IsRequired attribute that determines if the value of a property is required (a bit more on that later).

It does not contain anything relating to a regular expression or the maximum length, or indeed anything related to validation (except the IsRequired property and the ModelType which is used to validate that that the value can be converted to the type).

It is the HtmlHelper methods that are responsible for generating the html that is passed to the view. To generate the data-val-* attributes, your TextBoxFor() method internally calls the GetUnobtrusiveValidationAttributes() method of the HtmlHelper class which in turn calls methods in the DataAnnotationsModelValidatorProvider class which ultimately generate a Dictionary of the data-val attribute names and values used to generate the html.

I'll leave it to you to explore the source code if you want more detail (refer links below) but to summarize, it gets a collection of all attributes applied to your Truck property that inherit from ValidationAttribute to build the dictionary. In your case the only ValidationAttribute is [Some] which derives from RegularExpressionAttribute so the data-val-regex and data-val-regex-pattern attributes are added.

But because you have added your CustomRequiredAttribute in the TruckNumberMetadataProvider, the IsRequired property of the ModelMetadata has been set to true. If you insect the GetValidators() of DataAnnotationsModelValidatorProvider you will see that a RequiredAttribute is automatically added to the collection of attributes because you have not applied one to the property. The relevant snippet of code is

if (AddImplicitRequiredAttributeForValueTypes && metadata.IsRequired && !attributes.Any(a => a is RequiredAttribute))
{
    attributes = attributes.Concat(new[] { new RequiredAttribute() });
}

which results in the data-val-required attribute being added to the html (and it uses the default message because it knows nothing about your CustomRequiredAttribute)

Source code files to get you started if you want to understand the internal workings

  1. HtmlHelper.cs - refer GetUnobtrusiveValidationAttributes() methods at line 413
  2. ModelValidatorProviders.cs - gets the various ValidatorProviders used for validation
  3. DataAnnotationsModelValidatorProvider.cs - the ValidatorProvider for ValidationAttributes

One possible solution if you really want to use just one single ValidationAttribute would be to have it implement IClientValidatable and in the GetClientValidationRules() method, add rules, for example

var rule = new ModelClientValidationRule
{
    ValidationType = "required",
    ErrorMessage = "Required"
}

which will be read by the ClientDataTypeModelValidatorProvider (and delete your TruckNumberMetadataProvider class). However that will create a maintenance nightmare so I recommend you just add the 3 validation attributes to your property

这篇关于自定义 DataAnnotationsModelMetadataProvider 不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆