Interface Attributes != Class Attributes

问题

事情来源于很早之前Team成员一个不规范的设计,在MVC3的项目上,由于所有的Model都需要有一些基本的名称或者操作,加之应用了DI,所以就想当然地定义了一个接口,里面包含了一些接口属性和方法,可突然有一天要求在这些属性上应用一些验证约束和授权,于是接口代码改成了这样:

    public interface IModel
    {
        [Required]
        string ModelName { get; set; }

        [Permission(Configuration = "Debug")]
        void OutputMessage();
    }

实现类,几乎没有改变,只是在需要验证的属性上添加了Required类似的attribute:

    public class SearchCriteria : IModel
    {
        public string ModelName { get; set; }

        [Required]
        public string Keyword { get; set; }

        // 更多Model属性

        public void OutputMessage()
        {
            // 这里是处理代码
        }
    }

可是后来应用的时候,发现接口里定义的验证约束根本不起作用,后来开始一路查找。

分析

看了MVC的源码,找到了2个相关的地方。

1.查询所有的Attributes(AssociatedValidatorProvider里定义了GetValidators抽象方法)

        private IEnumerable<ModelValidator> GetValidatorsForType(ModelMetadata metadata, ControllerContext context) {
            return GetValidators(metadata, context, GetTypeDescriptor(metadata.ModelType).GetAttributes().Cast<Attribute>());
        }

        private IEnumerable<ModelValidator> GetValidatorsForProperty(ModelMetadata metadata, ControllerContext context) {
            ICustomTypeDescriptor typeDescriptor = GetTypeDescriptor(metadata.ContainerType);
            PropertyDescriptor property = typeDescriptor.GetProperties().Find(metadata.PropertyName, true);
            if (property == null) {
                throw new ArgumentException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        MvcResources.Common_PropertyNotFound,
                        metadata.ContainerType.FullName, metadata.PropertyName),
                    "metadata");
            }

            return GetValidators(metadata, context, property.Attributes.OfType<Attribute>());
        }

2.DataAnnotationsModelValidatorProvider类实现了GetValidators抽象方法,节选代码:

    // Produce a validator for each validation attribute we find
    foreach (ValidationAttribute attribute in attributes.OfType<ValidationAttribute>()) {
        DataAnnotationsModelValidationFactory factory;
        if (!AttributeFactories.TryGetValue(attribute.GetType(), out factory)) {
            factory = DefaultAttributeFactory;
        }
        results.Add(factory(metadata, context, attribute));
    }

可是从里面根本没有发现有什么特殊处理的地方,那为何就不能把接口的Attributes取出来呢?

后来看到Brad Wilson的一篇文章,才发现原来CLR对于基类和接口的处理是不一样的,基类是继承的,所以所有基类里出现的方法,属性等在子类实现类里具有同样的效果,但是接口是不一样的,接口是用来实现的,要求子类必须实现,但接口有2种实现方式:隐式和显式。

隐式:也就是我们上面的代码使用的方式,用public的属性和方法去实现接口,但是重要的,实现类里的public的属性和方法和接口里声明的那些完全不是一样的东西,虽然他们有相同的签名,通过反射代码,我们可以看出两者之间完全不一样,因为这个不同,所以说接口属性和方法上的metadata Attribute不会作用在实现类上,也就会出现我们文章开头的问题。

这些规则其实是CLR定义好的,和MVC无关,查看MVC里关于model绑定的源码,我们可以知道,在进行model绑定的时候,我们是基于实现类类型的,而不是接口类型,因为action里的参数一般来说都是实现类类型(MVC隐式创建),而不是接口类型(MVC实现不了),所以在上述2段MVC源码里通过反射查找Attributes的时候只能查询到实现类类型的Attributes。

改进

知道了原因,我们就得改代码了,第一种最简单的方式就是在子类实现类上需要验证的属性上逐一添加相应的验证类型Attributes,后来觉得其实Model绑定也没必要非要得DI挂上钩,所以改成了抽象类,如下:

    public abstract class ModelBase
    {
        [Required]
        string ModelName { get; set; }

        [Permission(Configuration = "Debug")]
        void OutputMessage();
    }

这样,就不用在子类实现类里逐一添加这些Attributes了。

当然,如果你非要使用接口,而又不想在子类里逐一添加Attributes,那恐怕你只有在MVC里使用自己的自定义ModelValidatorProvider了,在保留原来代码的基础上,加上一段特殊的逻辑,把该model所实现的接口逐一判断一下,看看里面有没有带ValidationAttribute,伪代码如下:

        public List<ValidationAttribute> GetValidationAttributesFromInterface()
        {
            //以类型SearchCriteria为例

            List<ValidationAttribute> attributes = new List<ValidationAttribute>();

            typeof(SearchCriteria)
                .GetInterfaces()
                .ToList()
                .ForEach(t =>
                        {
                            attributes.AddRange(
                                t.GetProperty("ModelName").
                                    GetCustomAttributes(true).OfType
                                    <ValidationAttribute>());
                        });

            return attributes;
        }

版权声明:本文为博主http://www.zuiniusn.com原创文章,未经博主允许不得转载。

时间: 2024-10-29 16:11:53

Interface Attributes != Class Attributes的相关文章

通过boundingRectWithSize:options:attributes:context:计算文本尺寸

转:http://blog.csdn.net/jymn_chen/article/details/10949279 之前用Text Kit写Reader的时候,在分页时要计算一段文本的尺寸大小,之前使用了NSString类的sizeWithFont:constrainedToSize:lineBreakMode:方法,但是该方法已经被iOS7 Deprecated了,而iOS7新出了一个boudingRectWithSize:options:attributes:context方法来代替: 很碍

Advanced Pricing - How to source Pricing Attributes using QP_CUSTOM_SOURCE.Get_Custom_Attribute_Valu

详细内容需要参考文档:Oracle 11i Advanced Pricing-Don't Customize, Extend! utl:http://blog.csdn.net/cai_xingyun/article/details/41384541 Oracle Advanced Pricing - Version 11.5.8 and later Information in this document applies to any platform. ***Checked for rele

QML Object Attributes

Every QML object type has a defined set of attributes. Each instance of an object type is created with the set of attributes that have been defined for that object type. There are several different kinds of attributes which can be specified, which ar

attributes &amp;&amp; reflections &amp;&amp;Thread&amp;&amp;Synchronization

1.属性和反射 属性是对目标元素的相关数据的代表. 同时C# 还具有一个反射系统,可用来检索用自定义属性定义的信息.(否则自定义属性没什么意义,不过老师说可以用一些软件来查看,比如书上说的:ILDasm) 老师讲的比较快,听得不是很清楚,后来翻了翻书,看到“自定义属性”的内容,进行了尝试. 这个例子是把程序的错误信息绑定到代码的特定修正中(特定修正?),然后利用反射将元数据输出到console流里. 1 namespace CustomAttributes 2 { 3 //create attr

Linear to physical address translation with support for page attributes

Embodiments of the invention are generally directed to systems, methods, and apparatuses for linear to physical address translation with support for?page attributes. In some embodiments, a system receives an instruction to translate a memory pointer

Spring 4 官方文档学习(十一)Web MVC 框架之Flash Attributes

接上一篇中的重定向. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-flash-attributes flash attributes提供了一种方式让一个请求保存attribute用于其他请求.这在重定向时很常见--例如,Post/Redirect/Get pattern.flash attributes会被临时的保存--在重定向之前,通常是保存在session中. 对

在Ubuntu Scope的模版中利用attributes来显示额外的信息

我在昨天的文章中介绍了我设计的优酷Scope.在今天的练习中,我将对它的模版做一些小的改动,利用模版中的attributes项使得它的显示更加生动. 如果感兴趣的朋友,可以在如下的地址下载最新的youku scope: git clone https://gitcafe.com/ubuntu/youku_keywords.git 首先,我们在query.cpp中对它的模版做如下的改动: query.cpp const std::string NORMAL_TEMPLATE = R"( { &qu

-boundingRectWithSize:options:attributes:context:用法

导入第三方一些类库后,出现一些警告就是某些方法被弃用了: 如: - (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(NSLineBreakMode)lineBreakModeNS_DEPRECATED_IOS(2_0,7_0, "Use -boundingRectWithSize:options:attributes:context:"); 提示用:boundingRec

Attributes.Add用途与用法

Attributes.Add("javascript事件","javascript语句"); 如: this.TextBox1.Attributes.add("onblue", "window.Label1.style.backgroundColor='#000000';"); this.TextBox1.Attributes.Add("onblur","this.style.display='n