每周源代码54 —— 用TypeDescriptor不能获取同一类型的多个属性

[原文发表地址] The Weekly Source Code 54 - Can't have Multiple Attributes of the Same Type when using a TypeDescriptor

[原文发表时间] 2010-07-03 12:48 AM

 

几周前我在中国,与一个叫Zhe Wang的人交谈,他正用ASP.NET动态数据创建网站。他制定了自己的自定义属性以便用于他的实体,并有效的制定了授权方案,这样只有特定的用户可以进行一些操作,其他用户却不可以。

 

在这个例子中,EntityAuthorize是Zhe自定义的带有一些特性的属性。

 

namespace MyModels.Metadata

{

    [EntityAuthorize(Roles = "Administrators", AllowedEntityActions = EntityActions.List | EntityActions.Edit)]

    [EntityAuthorize(Users = "Administrator", DeniedEntityActions = EntityActions.Edit)]

    public partial class SurveyMetaData

    {

        string Address { get; set; }

        string Content { get; set; }

        string Something { get; set; }

        EntityCollection<OtherStuff> OtherThings { get; set; }

    }

}

 

 

在这个例子中,他把两个属性放在了一个类中。顺便说一下,这没有什么不对。

 

后来,当要获取这些属性并处理的时候,奇怪的事情发生了。当他通过Dynamic Data中的常用方式MetaTable.Attributes.OfType<T>()来获取属性的时候,他只能得到一个。失望中他试图用很慢并且不好玩的GetCustomAttributes来获取。这是他很机智的解决方法(注意:这是没必要的,因为我们会用另一种方法解决这个问题)。

 

public static bool FilterVisible(this MetaTable table, EntityActions action)

{

   var usn = HttpContext.Current.User.Identity.Name;

   var roles = Roles.GetRolesForUser(usn);

   var attrs = table.Attributes.OfType<EntityAuthorizeAttribute>()

      .Where 

      (

         //try to get the attributes we need using the DynamicData metatable,

         // but we're only getting one of the attributes of this type.

      ).Union

      (

         //get it ourselves the slow way using GetCustomAttributes and it works.

         table.Attributes.OfType<MetadataTypeAttribute>()

         .Select(t => Attribute.GetCustomAttributes(t.MetadataClassType))

         .SelectMany(col => col).OfType<EntityAuthorizeAttribute>()

      );

 

      var allow = attrs.Any(a => a.AllowedEntityActions.HasFlag(action));

 

      var deny = attrs.Any(a => a.DeniedEntityActions.HasFlag(action));

 

      return allow && (!deny);

}

 

 

MetaTable.Attributes是如何流行的?通过一个标准的System.ComponentModel.TypeDescriptor提供程序,该程序获取所有的属性,合并在一起并放在集合里。但是,在这种情况下TypeDescriptor有点不是很好,因为系统内部有个异常的行为。

 

这有个简单的复制程序,可以显示TypeDescriptor未能获取两个属性的行为。TypeDescriptor API与标准的reflection API有些细微的差别。

 

class Program

{

    static void Main(string[] args)

    {

        var prop = TypeDescriptor.GetProperties(typeof(Product))["Num"];

        foreach (Attribute attrib in prop.Attributes)

        {

            Console.WriteLine(attrib.GetType());

        }

    }

}

 

public class Product

{

    [TestPermission("foo1", "bar1")]

    [TestPermission("foo2", "bar2")]

    public int Num { get; set; }

}

 

[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]

public class TestPermissionAttribute : Attribute

{

    public TestPermissionAttribute(string role, string user) { }

}

 

注意在trueAttributeUsage属性中,显性的AllowMultiple=true。但是,在Attribute(是的,System.Attribute)内部,有个叫TypeID的虚拟属性,属性的值在哈希表中变成了键值,并被TypeDescriptor用来删除重复值。在Attribute内部,我们可以看到:

 

public virtual object TypeId

{

    get

    {

        return base.GetType();

    }

}

 

可能应该是这样的一些类似代码,但不是(伪码):

 

public virtual TypeID {

    get {

        if (IsAllowMultiple()) return this;

        return GetType();

    }

}

 

因为不是真正的这样,但它是虚拟的,你当然可以改变你的TypeID执行,返回像如下这样:

 

[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]

public class TestPermissionAttribute : Attribute

{

    public TestPermissionAttribute(string role, string user) { }

 

    public override object TypeId { get { return this; } }

}

 

但是,在这个简单的例子中,对象没有状态。注意参数没有被存储,所以“这”会是同样的对象。添加一些字段,对象就不同了,因为现在他们有一些状态,例程返回如下TypeID:

 

using System;

using System.ComponentModel;

class Program

{

    static void Main(string[] args)

    {

        var prop = TypeDescriptor.GetProperties(typeof(Product))["Num"];

        foreach (Attribute attrib in prop.Attributes)

        {

            Console.WriteLine(attrib.GetType());

        }

        Console.ReadLine();

    }

}

 

public class Product

{

    [TestPermission("foo1", "bar1")]

    [TestPermission("foo2", "bar2")]

    public int Num { get; set; }

}

 

[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]

public class TestPermissionAttribute : Attribute

{

    public TestPermissionAttribute(string role, string user) { Role = role; User = user; }

 

    private string Role;

    private string User;

 

    public override object TypeId { get { return this; } }

}

 

所以,正确的方法是,如果你用TypeDescriptors获取CustomAttributes,并且你想要同一类的多个属性,你要确保:

 

重载TypeId以便返回this

确保AllowMultiple = true

确保你的对象有一些字段(即状态)

 

感谢Zhe Wang 和 David Ebbo的示例和帮助。

 

我希望这两个人(他们使得这个文章能更加深入)以及曾经遇到到了类似情况的人会喜欢这篇博客。

 

如果你感兴趣,可以Twitter我 : @shanselman