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.
[AttributeUsage(AttributeTargets.Property)]
public class SecuredAttribute : Attribute {
public string[] Roles { get; private set; }
public SecuredAttribute() {}
public SecuredAttribute(string[] roles) {
Roles = roles;
}
}
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:
public class Person {
public int ID { get; set; }
[Secured(new string[] { "Admin", "Manager" })] public string Name { get;
set; }
[Secured(new string[] { "Admin", "HR", "" })] public int Age { get; set; }
public Person() {}
}
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:
public class RoleProvider {
public RoleProvider() {}
public static string[] GetPersonRoles() {
// Can be fecthed via a lookup but just fake it for now
return new string[] { "Admin", "Users", "PowerUsers" };
}
}
public class Person {
public int ID { get; set; }
[Secured(RoleProvider.GetPersonRoles())]
public string Name { get; set; }
[Secured(RoleProvider.GetPersonRoles())]
public int Age { get; set; }
}
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.
/// <summary>
/// An interface representing any type which needs to be secured.
/// </summary>
public interface ISecurable {
/// <summary>
/// Gets the roles which have access to an instance of a type which implements
/// this interface.
/// </summary>
/// <returns></returns>
string[] GetRoles();
/// <summary>
/// Gets the roles which have access to the specified property of an instance
/// of a type which implements this interface.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
string[] GetRoles(string propertyName);
}
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.
public class Person : ISecurable {
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Person() {}
#region ISecurable Members
public string[] GetRoles() {
// Look up the roles that can access all the artifacts of this instance from
// some data source, for now let's fake it.
return new string[] { "Admin" };
}
public string[] GetRoles(string propertyName) {
// Look up the roles that can access the specified property from some data
// source, for now let's fake it.
if (ID == 1) {
switch (propertyName) {
case "ID": {
return new string[] { "Power Users" };
}
case "Name": {
return new string[] { "Power Users", "Users" };
}
default: {
return new string[] {};
}
}
} else {
switch (propertyName) {
case "ID": {
return new string[] { "Power Users" };
}
case "Name": {
return new string[] { "Power Users" };
}
default: {
return new string[] {};
}
}
}
}
#endregion
}
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.
/// A generic custom type descriptor for the specified type
/// </summary>
public sealed class CustomTypeDescriptionProvider<T> : TypeDescriptionProvider
where T : ISecurable {
/// <summary>
/// Constructor
/// </summary>
public CustomTypeDescriptionProvider(TypeDescriptionProvider parent)
: base(parent) {}
/// <summary>
/// Create and return a custom type descriptor and chains it with the original
/// custom type descriptor
/// </summary>
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
object instance) {
return new SecuredAttributeCustomTypeDescriptor<T>(
base.GetTypeDescriptor(objectType, instance));
}
}
/// <summary>
/// A custom type descriptor which attaches a <see cref="SecuredAttribute"/> to
/// an instance of a type which implements <see cref="ISecurable"/>
/// </summary>
public sealed class SecuredAttributeCustomTypeDescriptor<T>
: CustomTypeDescriptor
where T : ISecurable {
/// <summary>
/// Constructor
/// </summary>
public SecuredAttributeCustomTypeDescriptor(ICustomTypeDescriptor parent)
: base(parent) {}
public override AttributeCollection GetAttributes() {
Type securableType = typeof(T).GetInterface(typeof(ISecurable).Name);
if (securableType != null) {
ISecurable securableInstance = GetPropertyOwner(
base.GetProperties().Cast<PropertyDescriptor>().First())
as ISecurable;
string[] instanceLevelRoles = securableInstance.GetRoles();
List<Attribute> attributes =
new List<Attribute>(base.GetAttributes().Cast<Attribute>());
SecuredAttribute securedAttrib = new SecuredAttribute(instanceLevelRoles);
TypeDescriptor.AddAttributes(securableInstance, securedAttrib);
attributes.Add(securedAttrib);
return new AttributeCollection(attributes.ToArray());
}
return base.GetAttributes();
}
/// <summary>
/// This method add a new property to the original collection
/// </summary>
public override PropertyDescriptorCollection GetProperties() {
// Enumerate the original set of properties and create our new set with it
PropertyDescriptorCollection originalProperties = base.GetProperties();
List<PropertyDescriptor> newProperties = new List<PropertyDescriptor>();
Type securableType = typeof(T).GetInterface("ISecurable");
if (securableType != null) {
foreach (PropertyDescriptor pd in originalProperties) {
ISecurable securableInstance = GetPropertyOwner(pd) as ISecurable;
string[] propertyRoles = securableInstance.GetRoles(pd.Name);
SecuredAttribute securedAttrib = new SecuredAttribute(propertyRoles);
// Create a new property and add it to the collection
PropertyDescriptor newProperty = TypeDescriptor.CreateProperty(
typeof(T), pd.Name, pd.PropertyType, securedAttrib);
newProperties.Add(newProperty);
}
// Finally return the list
return new PropertyDescriptorCollection(newProperties.ToArray(), true);
}
return base.GetProperties();
}
}
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.
public class Program {
private static void DisplayRoles(Person person) {
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Displaying roles required to access Person with ID: {0}",
person.ID.ToString());
Console.ResetColor();
SecuredAttribute securedAttrib =
TypeDescriptor.GetAttributes(person).Cast<Attribute>().SingleOrDefault(
a => a.GetType().Name == typeof(SecuredAttribute).Name)
as SecuredAttribute;
if (securedAttrib != null) {
string[] roles = securedAttrib.Roles;
if (roles.Length > 0) {
Console.WriteLine("Instance Level Roles");
Console.WriteLine("====================");
string strRoles = roles.Aggregate((r1, r2) => r1 + "," + r2);
Console.WriteLine("Roles: {0}", roles);
}
}
person.GetType().GetProperties().ToList().ForEach(propInfo => {
PropertyDescriptor propDescriptor =
TypeDescriptor.GetProperties(person)
.Cast<PropertyDescriptor>()
.SingleOrDefault(p => propInfo.Name == p.Name);
// if there is a property descriptor defined for the current property then
// proceed to read roles defined for this property
if (propDescriptor != null) {
SecuredAttribute attrib =
propDescriptor.Attributes.Cast<Attribute>().SingleOrDefault(
p => p.GetType().Name == typeof(SecuredAttribute).Name)
as SecuredAttribute;
// ensure that the property has been Secured.
if (attrib != null) {
string[] roles = attrib.Roles;
if (roles.Length > 0) {
Console.WriteLine(Environment.NewLine +
"Property Name:" + propInfo.Name);
Console.WriteLine("==============");
string strRoles = roles.Aggregate((r1, r2) => r1 + "," + r2);
Console.WriteLine("Roles: {0}", strRoles);
}
}
}
});
}
static void Main(string[] args) {
Person person1 = new Person { ID = 1, Age = 23, Name = "Jack Smith" };
TypeDescriptor.AddProvider(new CustomTypeDescriptionProvider<Person>(
TypeDescriptor.GetProvider(typeof(Person))),
person1);
Person person2 = new Person { ID = 2, Age = 24, Name = "Jane Smith" };
TypeDescriptor.AddProvider(new CustomTypeDescriptionProvider<Person>(
TypeDescriptor.GetProvider(typeof(Person))),
person2);
DisplayRoles(person1);
DisplayRoles(person2);
}
}
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#…
Posted on Saturday, January 10, 2009 9:37 PM C# | Back to top