字段由同一属性多次注释

时间:2014-08-20 20:58:53

标签: .net asp.net-mvc validation data-annotations

对于我的ASP.NET MVC应用程序,我创建了自定义验证属性,并指出可以为单个字段或属性指定多个实例:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class SomeAttribute: ValidationAttribute

我为这样的属性创建了验证器:

public class SomeValidator : DataAnnotationsModelValidator<SomeAttribute>

并将其连接到Global.asax的Application_Start

DataAnnotationsModelValidatorProvider.RegisterAdapter(
    typeof (SomeAttribute), typeof (SomeValidator));

最后,如果我以所需的方式使用我的属性:

[SomeAttribute(...)]  //first
[SomeAttribute(...)]  //second
public string SomeField { get; set; }

当框架执行验证时,只调用第一个属性实例。第二个似乎死了。我注意到在每个请求期间只创建了一个验证器实例(对于第一个注释)。

如何解决此问题并触发所有属性?

1 个答案:

答案 0 :(得分:7)

让我自己回答。来自MSDN(http://msdn.microsoft.com/en-us/library/system.attribute.typeid.aspxhttp://msdn.microsoft.com/en-us/library/6w3a7b50.aspx):

  

使用时定义自定义属性   AttributeUsageAttribute.AllowMultiple设置为true,您必须覆盖   Attribute.TypeId属性使其唯一。如果所有的实例   您的属性是唯一的,重写Attribute.TypeId以返回   属性的对象标识。如果只是你的一些实例   属性是唯一的,从Attribute.TypeId返回一个值   在这些情况下返回平等。例如,某些属性有一个   构造函数参数,充当唯一键。对于这些属性,   从中返回构造函数参数的值   Attribute.TypeId属性。

     

如实现的那样,该标识符仅仅是属性的类型。   但是,意图使用唯一标识符来识别   两个相同类型的属性。

总结:

TypeId被记录为&#34;唯一标识符,用于标识相同类型的两个属性&#34;。默认情况下,TypeId只是属性的类型,因此当遇到相同类型的两个属性时,它们会被视为&#34;相同的&#34;通过MVC验证框架。

实施:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class SomeAttribute: ValidationAttribute
{
    private string Parameter { get; set; }
    public override object TypeId
    {
        get { return string.Format("{0}[{1}]", GetType().FullName, Parameter); } 
    }

    public SomeAttribute(string parameter)
    {
        Parameter = parameter;
    }                

选择TypeId创建方式而不是以下方法:

  • 返回新对象 - 它太多了,实例总是不同的,
  • 基于字符串参数返回哈希码 - 可能导致冲突(无限多个字符串无法被内射映射到任何有限集中 - 字符串的最佳唯一标识符是字符串本身,请参阅here)。

完成后,服务器端验证案例工作。当这个想法需要与不引人注目的客户端验证相结合时,问题就开始了。假设您已按以下方式定义了自定义验证器:

public class SomeAttributeValidator : DataAnnotationsModelValidator<SomeAttribute>
{
    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        var rule = new ModelClientValidationRule {ValidationType = "someattribute"};
        rule.ValidationParameters.Add(...)
        yield return rule;
    }

有这个:

public class Model
{
    [SomeAttribute("first")]
    [SomeAttribute("second")]
    public string SomeField { get; set; }

导致以下错误:

  

不显眼的客户端验证规则中的验证类型名称必须是   独特。以下验证类型不止一次出现:   someattribute

如上所述,解决方案是拥有独特的验证类型。我们必须区分每个已注册的属性实例,它已用于注释字段,并为其提供相应的验证类型:

public class SomeAttributeValidator : DataAnnotationsModelValidator<SomeAttribute>
{
    private string AnnotatedField { get; set; }
    public SomeAttributeValidator(
        ModelMetadata metadata, ControllerContext context, SomeAttribute attribute)
        : base(metadata, context, attribute)
    {
        AnnotatedField = string.Format("{0}.{1}", 
            metadata.ContainerType.FullName, metadata.PropertyName);
    }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        var count = Storage.Get<int>(AnnotatedField) + 1;
        Storage.Set(AnnotatedField, count);

        var suffix = char.ConvertFromUtf32(96 + count); // gets a lowercase letter 
        var rule = new ModelClientValidationRule
        {
            ValidationType = string.Format("someattribute{0}", suffix)
        };

(验证类型必须只包含小写字母 - 使用上面的解决方案,如果你有超过26个属性 - 整个字母表用尽,预计会有异常)

其中Storage是一个简单的实用程序,用于存储当前http请求的数据:

internal class Storage
{
    private static IDictionary Items
    {
        get
        {
            if (HttpContext.Current == null)
                throw new ApplicationException("HttpContext not available.");
            return HttpContext.Current.Items;
        }
    }

    public static T Get<T>(string key)
    {
        if (Items[key] == null)
            return default(T);
        return (T)Items[key];
    }

    public static void Set<T>(string key, T value)
    {
        Items[key] = value;
    }
}

最后,JavaScript部分:

$.each('abcdefghijklmnopqrstuvwxyz'.split(''), function(idx, val) {
    var adapter = 'someattribute' + val;
    $.validator.unobtrusive.adapters.add(adapter, [...], function(options) {
        options.rules[adapter] = {
            ...
        };
        if (options.message) {
            options.messages[adapter] = options.message;
        }
    });
});

$.each('abcdefghijklmnopqrstuvwxyz'.split(''), function(idx, val) {
    var method = 'someattribute' + val;
    $.validator.addMethod(method, function(value, element, params) {
        ...
    }, '');
});

要获得完整的解决方案,go through this sources