Dynamic Attributes in C#

Things have been busy and it's been a while since my last post, so I decided to write up a post today related to something I have been working on recently where in I needed a way to associate an attribute with a class and some of it's properties, but the values I wanted to pass in to the attribute constructor needed to be dynamic.

We all know that attributes are; classes that can be tagged onto code artifacts such as Methods,Properties,Events,Delegates,Classes etc. When you tag a code element with an attribute, it is stored in the assembly of the target artifact as metadata. At runtime, you can then query an instance of a class/struct on which the attribute is defined (using reflection fo course) to find the attributes defined on on an artifact and can then take appropriate action.

In the following code sample we have created an attribute class called SecuredAttribute which is used to store a collection of rolenames.

 
  1. [AttributeUsage(AttributeTargets.Property)]  
  2.     public class SecuredAttribute : Attribute  
  3.     {  
  4.       
  5.         public string[] Roles { getprivate set; }  
  6.   
  7.         public SecuredAttribute()  
  8.         {  
  9.         }  
  10.         public SecuredAttribute(string[] roles)  
  11.         {  
  12.             Roles = roles;  
  13.         }  
  14.     }  

The attribute is intended to indicate the roles that the calling thread's principal must have for accesing the artifact that has been decorated by this attribute.
For simplicity, the attribute has been marked with AttributeUsageTargets.Property to indicate that it can only be applied to Properties but this can be extended to other targets(artifacts) as well.

We have also created a simple Person class with properties - Name and Age.
These properties are decorated with the Secured attribute.

We want to specify the roles that the calling thread's principal needs to belong to in order to access a property.We can easily do this, like so:

 
  1. public class Person  
  2. {  
  3.     public int ID { getset; }  
  4.   
  5.     [Secured(new string[] { "Admin""Manager"})]  
  6.     public string Name {getset;}  
  7.   
  8.     [Secured(new string[] { "Admin""HR""" })]  
  9.     public int Age {getset;}  
  10.   
  11.   
  12.     public Person()  
  13.     {  
  14.           
  15.     }  
  16. }  
The drawback with this approach is that the role names are hard wired to the code.
What if we wanted to read this information dynamically.So with that in mind, what if we re-write the above code as follows:
 
  1. public class RoleProvider  
  2. {  
  3.     public RoleProvider()  
  4.     {  
  5.     }  
  6.   
  7.     public static string[] GetPersonRoles()  
  8.     {  
  9.         //Can be fecthed via a lookup but just fake it for now  
  10.         return new string[] { "Admin""Users""PowerUsers" };  
  11.     }  
  12. }  
  13.   
  14. public class Person   
  15. {  
  16.     public int ID { getset; }  
  17.   
  18.     [Secured(RoleProvider.GetPersonRoles())]  
  19.     public string Name {getset;}  
  20.   
  21.     [Secured(RoleProvider.GetPersonRoles())]  
  22.     public int Age { getset; }  
  23. }  

Does this compile? <G>


The compiler throws back this lovely error message:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
This means that we cannot achieve what we set out to do using attributes.
Or can we...?

Enter TypeDescriptor. A not so widely known(rather used) type from the System.ComponentModel namespace.
A complete treatment of TypeDescriptors is beyond the scope of this article but in a nutshell, TypeDescriptors provide a mechanism for dynamically associating and querying metadata on instances at runtime. It also complements the .NET reflection APIs.
TypeDescriptors are used heavily in the Visual Studio Designer property grids for manipulating properties of controls. Although TypeDescriptors are intended for use with components and probably well known in the control developer community, there is nothing that prevents it's use with plain old non-component .NET objects.

TypeDescriptors allow creating and associating attributes dynamically, moreover the attribute constructors are not limited to just constant expressions, typeof expressions or array creation expressions.

Hence, a custom attribute constructor can now accept as paramters, any .NET type including generics!.

This is particularly useful in our case since we need to dynamically specify a collection of rolenames at runtime to initialize the the SecuredAttribute constructor.

The caveat is that the attribute is not specified on the code artifact statically, but instead we do this dynamically by writing a CustomTypeDescriptor and a correponding provider. But before we do this, we need to introduce the following entity to help us along the way.

 
  1. /// <summary>  
  2. /// An interface representing any type which needs to be secured.  
  3. /// </summary>  
  4. public interface ISecurable  
  5. {  
  6.     /// <summary>  
  7.     /// Gets the roles which have access to an instance of a type which implements this interface.  
  8.     /// </summary>  
  9.     /// <returns></returns>  
  10.     string[] GetRoles();  
  11.   
  12.     /// <summary>  
  13.     /// Gets the roles which have access to the specified property of an instance of a type which implements this interface.  
  14.     /// </summary>  
  15.     /// <param name="propertyName">Name of the property.</param>  
  16.     /// <returns></returns>  
  17.     string[] GetRoles(string propertyName);  
  18. }  
ISecurable represents any type for which we want to associate security information.
It provides a mechanism to query the underlying instance to retrieve the roles which can access the instance aand or a specific property.
The Person class implements this so that each instance of it can indicate the roles the caller needs to be in to access the instance or one of its properties.
 
  1. public class Person : ISecurable  
  2. {  
  3.     public int ID { getset; }  
  4.         
  5.     public string Name {getset;}  
  6.         
  7.     public int Age { getset; }  
  8.     
  9.     public Person()  
  10.     {              
  11.     }  
  12.  
  13.     #region ISecurable Members  
  14.   
  15.     public string[] GetRoles()  
  16.     {  
  17.         //Look up the roles that can access all the artifacts of this instance from some data source, for now let's fake it.  
  18.         return new string[] { "Admin"};  
  19.     }  
  20.   
  21.     public string[] GetRoles(string propertyName)  
  22.     {  
  23.         //Look up the roles that can access the specified property from some data source, for now let's fake it.  
  24.         if (ID == 1)  
  25.         {  
  26.             switch (propertyName)  
  27.             {  
  28.                 case "ID":  
  29.                     {  
  30.                         return new string[] { "Power Users" };  
  31.                     }  
  32.                 case "Name":  
  33.                     {  
  34.                         return new string[] { "Power Users","Users" };  
  35.                     }  
  36.                 default:  
  37.                     {  
  38.                         return new string[] { };  
  39.                     }  
  40.             }  
  41.         }  
  42.         else  
  43.         {  
  44.             switch (propertyName)  
  45.             {  
  46.                 case "ID":  
  47.                     {  
  48.                         return new string[] {"Power Users"};  
  49.                     }  
  50.                 case "Name":  
  51.                     {  
  52.                         return new string[] {"Power Users"};  
  53.                     }  
  54.                 default:  
  55.                     {  
  56.                         return new string[] {};  
  57.                     }  
  58.             }  
  59.         }  
  60.     }  
  61.  
  62.     #endregion  
  63. }  
With this in place we are now ready to write a CustomTypeDescriptor and provider. All the descriptor does is dynamically create instances of SecuredAttribute and associates them with the instance.
 
  1. /// A generic custom type descriptor for the specified type  
  2.   /// </summary>  
  3.   public sealed class CustomTypeDescriptionProvider<T> : TypeDescriptionProvider where T : ISecurable  
  4.   {  
  5.       /// <summary>  
  6.       /// Constructor  
  7.       /// </summary>  
  8.       public CustomTypeDescriptionProvider(TypeDescriptionProvider parent)  
  9.           : base(parent)  
  10.       {  
  11.       }  
  12.   
  13.       /// <summary>  
  14.       /// Create and return a custom type descriptor and chains it with the original  
  15.       /// custom type descriptor  
  16.       /// </summary>  
  17.       public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)  
  18.       {  
  19.           return new SecuredAttributeCustomTypeDescriptor<T>(base.GetTypeDescriptor(objectType, instance));  
  20.       }  
  21.   }  
  22.   
  23.   /// <summary>  
  24.   /// A custom type descriptor which attaches a <see cref="SecuredAttribute"/> to   
  25.   /// an instance of a type which implements <see cref="ISecurable"/>  
  26.   /// </summary>  
  27.   public sealed class SecuredAttributeCustomTypeDescriptor<T> : CustomTypeDescriptor where T : ISecurable  
  28.   {  
  29.       /// <summary>  
  30.       /// Constructor  
  31.       /// </summary>  
  32.       public SecuredAttributeCustomTypeDescriptor(ICustomTypeDescriptor parent)  
  33.           : base(parent)  
  34.       {  
  35.       }  
  36.   
  37.       public override AttributeCollection GetAttributes()  
  38.       {  
  39.           Type securableType = typeof(T).GetInterface(typeof(ISecurable).Name);  
  40.           if (securableType != null)  
  41.           {  
  42.               ISecurable securableInstance = GetPropertyOwner(base.GetProperties().Cast<PropertyDescriptor>().First()) as ISecurable;  
  43.               string[] instanceLevelRoles = securableInstance.GetRoles();  
  44.               List<Attribute> attributes = new List<Attribute>(base.GetAttributes().Cast<Attribute>());  
  45.               SecuredAttribute securedAttrib = new SecuredAttribute(instanceLevelRoles);  
  46.               TypeDescriptor.AddAttributes(securableInstance, securedAttrib);  
  47.               attributes.Add(securedAttrib);  
  48.               return new AttributeCollection(attributes.ToArray());  
  49.           }  
  50.           return base.GetAttributes();  
  51.   
  52.       }  
  53.       /// <summary>  
  54.       /// This method add a new property to the original collection  
  55.       /// </summary>  
  56.       public override PropertyDescriptorCollection GetProperties()  
  57.       {  
  58.           // Enumerate the original set of properties and create our new set with it  
  59.           PropertyDescriptorCollection originalProperties = base.GetProperties();  
  60.           List<PropertyDescriptor> newProperties = new List<PropertyDescriptor>();  
  61.           Type securableType = typeof(T).GetInterface("ISecurable");  
  62.           if (securableType != null)  
  63.           {  
  64.               foreach (PropertyDescriptor pd in originalProperties)  
  65.               {  
  66.                   ISecurable securableInstance = GetPropertyOwner(pd) as ISecurable;  
  67.                   string[] propertyRoles = securableInstance.GetRoles(pd.Name);  
  68.                   SecuredAttribute securedAttrib = new SecuredAttribute(propertyRoles);  
  69.                   // Create a new property and add it to the collection  
  70.                   PropertyDescriptor newProperty = TypeDescriptor.CreateProperty(typeof(T), pd.Name, pd.PropertyType, securedAttrib);  
  71.                   newProperties.Add(newProperty);  
  72.               }  
  73.               // Finally return the list  
  74.               return new PropertyDescriptorCollection(newProperties.ToArray(), true);  
  75.           }  
  76.           return base.GetProperties();  
  77.       }  
  78.   }  
We have overridden GetAttributes in which we query the underlying instance to fetch the roles that can access all the artifacts of the underlying instance.
Similarly, we have also overridden GetProperties which will in turn query the instance to fetch the roles that can access the specified property name. We are now ready to associate the SecuredAttribute with Person instances.
 
  1. public class Program  
  2.  {  
  3.   
  4.      private static void DisplayRoles(Person person)  
  5.      {  
  6.          Console.ForegroundColor = ConsoleColor.Cyan;  
  7.          Console.WriteLine("Displaying roles required to access Person with ID: {0}", person.ID.ToString());  
  8.          Console.ResetColor();               
  9.            
  10.          SecuredAttribute securedAttrib = TypeDescriptor.GetAttributes(person).Cast<Attribute>().SingleOrDefault(a => a.GetType().Name == typeof(SecuredAttribute).Name) as SecuredAttribute;  
  11.   
  12.              if (securedAttrib != null)  
  13.              {  
  14.                  string[] roles = securedAttrib.Roles;  
  15.                  if (roles.Length > 0)  
  16.                  {  
  17.                      Console.WriteLine("Instance Level Roles");  
  18.                      Console.WriteLine("====================");  
  19.                      string strRoles = roles.Aggregate((r1,r2)=>  r1+ "," + r2);  
  20.                      Console.WriteLine("Roles: {0}",roles);  
  21.                  }  
  22.              }  
  23.              person.GetType().GetProperties().ToList().ForEach  
  24.                  (propInfo =>  
  25.                  {  
  26.                      PropertyDescriptor propDescriptor = TypeDescriptor.GetProperties(person).Cast<PropertyDescriptor>().SingleOrDefault(p => propInfo.Name == p.Name);  
  27.   
  28.                      //if there is a property descriptor defined for the current property then proceed to read roles defined for this property  
  29.                      if (propDescriptor != null)  
  30.                      {  
  31.                          SecuredAttribute attrib = propDescriptor.Attributes.Cast<Attribute>().SingleOrDefault  
  32.                                              (p => p.GetType().Name == typeof(SecuredAttribute).Name) as SecuredAttribute;  
  33.   
  34.                          //ensure that the property has been Secured.  
  35.                          if (attrib != null)  
  36.                          {  
  37.                              string[] roles = attrib.Roles;  
  38.                              if (roles.Length > 0)  
  39.                              {  
  40.                                  Console.WriteLine(Environment.NewLine + "Property Name:" + propInfo.Name);  
  41.                                  Console.WriteLine("==============");  
  42.                                  string strRoles = roles.Aggregate((r1, r2) => r1 + "," + r2);  
  43.                                  Console.WriteLine("Roles: {0}", strRoles);                                                                                      
  44.                              }  
  45.                          }  
  46.                      }  
  47.                  }  
  48.                   );  
  49.            
  50.      }  
  51.   
  52.   
  53.      static void Main(string[] args)  
  54.      {  
  55.          Person person1 = new Person { ID = 1, Age = 23, Name = "Jack Smith" };  
  56.          TypeDescriptor.AddProvider(new CustomTypeDescriptionProvider<Person>(TypeDescriptor.GetProvider(typeof(Person))), person1);  
  57.   
  58.          Person person2 = new Person { ID = 2, Age = 24, Name = "Jane Smith" };  
  59.          TypeDescriptor.AddProvider(new CustomTypeDescriptionProvider<Person>(TypeDescriptor.GetProvider(typeof(Person))), person2);  
  60.   
  61.          DisplayRoles(person1);  
  62.          DisplayRoles(person2);  
  63.      }  
  64.  }  

We are associating the TypeDescriptors in Main itself for the purposes of illustration, but this can easily be refactored into a factory class.
The output of the above code indicates that attributes are indeed being applied and queried for dynamically.

Next steps:

  • Associating the SecuredAttribute with artifacts besides Properties, such as events, methods etc.
  • We haven't specified how roles are associated with the calling thread's identity, or specifically how roles are associated with the calling thread's identity on a per ISecurable instance basis.
  • We haven't associated any permissions with roles.
  • An API is needed that ties the above 2 and provides a mechanism to perform an access control check.
This is left as an exercise for the reader. If I have some spare time we'll have a Part 2 to cover the above steps.
Happy coding....

References:
Type Descriptor Class
Understanding the TypeDescriptor: A Metadata Engine for Designtime Code
Adding Attributes to Properties At Runtime in C#...


kick it on DotNetKicks.com

Print | posted @ Saturday, January 10, 2009 9:37 PM

Comments on this entry:

Gravatar # re: Dynamic Attributes in C#
by Mandrake at 1/3/2010 2:25 PM

Great post. This weekend I've been reading up on stuff like this, Dynamic Proxies and Reflection.Emit - has opened my eyes to a lot of creative ideas out there!

Thanks.
Gravatar # re: Dynamic Attributes in C#
by kmoo01 at 2/16/2010 6:53 AM

definitely an eye opener!
Do you think this could be used for extending the ASP.Net Security model, i.e. being able to dynamically add a role to the PrincipalPermissionAttribute ?
Gravatar # re: Dynamic Attributes in C#
by Alex at 3/16/2010 9:58 AM

I'd like to use this on .NET 2.0. It doesn't build because of the generic Cast methods that are from 3.5

ISecurable securableInstance = GetPropertyOwner(base.GetProperties().Cast<PropertyDescriptor>().First()) as ISecurable;

Any idea on how to rewrite this for 2.0?

Thanks!
Gravatar # re: Dynamic Attributes in C#
by Abhijeet at 3/16/2010 9:15 PM

@Alex: You can try this-
ISecurable securableInstance = GetPropertyOwner(base.GetProperties()[0]) as ISecurable;

I would suggest adding a check to ensure that base.GetProperties() returns at least one element before you index into the result of base.GetProperties().

Hope this helps
Gravatar # re: Dynamic Attributes in C#
by Abhijeet at 3/16/2010 9:59 PM

@kmoo01: That's a great question. The PrincipalPermissionAttribute does not allow for adding additional roles, you can only get or set a single role.
That said, if you wanted to dynamically add a PrincipalPermissionAttribute to an object you can definitely do that. You'll need to create a derived TypeDescriptor similar to the one I have for SecuredAttributeCustomTypeDescriptor and in the GetAttributes() you'd add the attribute like so:

PrincipalPermissionAttribute permAttrib = new PrincipalPermissionAttribute(SecurityAction.Demand);
permAttrib.Role = "Administrator";
TypeDescriptor.AddAttributes(securableInstance, securedAttrib);

and in GetProperties() you'd add the property like so:

PrincipalPermissionAttribute permAttrib = new PrincipalPermissionAttribute(SecurityAction.Demand);
permAttrib.Role = "Administrator";
PropertyDescriptor newProperty = TypeDescriptor.CreateProperty(typeof(T), pd.Name, pd.PropertyType, permAttrib);

In the client you can query for the attribute with something like this:
PrincipalPermissionAttribute permAttrib = TypeDescriptor.GetAttributes(person).Cast<Attribute>().SingleOrDefault(a => a.GetType().Name == typeof(PrincipalPermissionAttribute).Name) as PrincipalPermissionAttribute;

if(permAttrib != null)
{
Console.WriteLine(permAttrib.Role);
}

Hope this helps.
Gravatar # re: Dynamic Attributes in C#
by Julio at 7/9/2011 2:08 PM

Hi, very nice but the code won't work in Mono. Tested with mono 2.10.2, to no avail.
Gravatar # re: Dynamic Attributes in C#
by jeybee at 5/9/2012 2:00 AM

Is there an easy way to apply this solution when it comes to adding dynamic attributes to methods ?? I would think it differs a bit, we need to deal with separate methods, not with instances of the same class. If someone could give me some advice, point the direction, cause I'm not very familiar with .NET :)
Post A Comment
Title:
Name:
Email:
Comment:
Verification: