in

ASPXWizard.net

.net and Ajax Community

Mina Labib

Run time (Dynamic) attributes in C#

In the current project I was concerned to build generic user control, accept generic collection and paint collection elements details in HTML table and allow editing functionality for specific columns. The challenge is to detect the editable properties in the collection elements in the run time without knowing the collection or columns types.

My idea was to decorate desired properties with custom Attributes to specify the layout behavior; Attributes can be accessed and obtained using Reflection in the run time, the idea seems working fine.

So, what are attributes in c#?

"Attributes provide a powerful method of associating declarative information with C# code (types, methods, properties, and so forth). Once associated with a program entity, the attribute can be queried at run time and used in any number of way" - MSDN.

Also attributes are classes that inherits System.Attribute as below

[AttributeUsage(AttributeTargets.Property)]

public class UIAttributes : Attribute

{

private bool isReadOnly = false;

public bool IsReadOnly

{

get { return isReadOnly; }

}

public UIAttributes(bool isReadOnly)

{

this.IsReadOnly = isReadOnly;

}

}

Then decorate class property with the custom attribute:

public class ProductViewData

{

public string Name { get; set; }

[UIAttributes(true)] //ProdcutID property is decorated to be read only.

public int ProductID { get; set; }

public double Price { get; set; }

}

Then by using reflection we can detect which property has the custom attribute and paint attribute according to that.

public void DetectProperties(object o)

{

PropertyInfo[] propInfo = o.GetType().GetProperties();

foreach(PropertyInfo prop in propInfo)

{

foreach(Attribute att in prop.GetCustomAttributes(true))

{

if((att is UIAttributes) && (((UIAttributes)att).IsReadOnly) )

{

//Do somthing.

}

}

}

}

But let's say the same property should be editable in a view and non-editable in other view, then property behavior should be changed at run time to toggle between the editable/read-only mode, and custom attributes does now allow to be changed at runtime.

So, after some readings there was no easy way to do that, but there was a great class in System.ComponentModel namespace called TypeDescriptor which allow dynamically assigning and querying metadata on instance on runtime, it seems like it is our solution.

The only drawback is the long way to implement it, so first I built Interface representing any type need to follow my custom attribute:

Interface:

public interface IUIAttributes

{

bool IsReadOnly(string PName);

bool IsReadOnly();

}

Custom Attribute:

[AttributeUsage(AttributeTargets.Property)]

public class UIAttributes : Attribute

{

private bool isReadOnly = false;

public bool IsReadOnly

{

get { return isReadOnly; }

set { isReadOnly = value; }

}

public UIAttributes()

{

}

public UIAttributes(bool isReadOnly)

{

this.IsReadOnly = isReadOnly;

}

}

Any Class implements the interface:

public class ProductViewData : IUIAttributes

{

public string Name { get; set; }

public int ProductID { get; set; }

public double Price { get; set; }

#region IUIAttributes Members

bool IUIAttributes.IsReadOnly(string PName)

{

if (PName.Equals("ProductID"))

return true;

else

return false;

}

bool IUIAttributes.IsReadOnly()

{

return false;

}

#endregion

}

Now I have to write a CustomTypeDescriptor and Provider, this descriptor will create instances of my custom attribute UIAttribute and associate them to types that implement the Interface:

public sealed class CustomTypeDescriptionProvider : TypeDescriptionProvider where T : IUIAttributes

{

///

/// Constructor

///

public CustomTypeDescriptionProvider(TypeDescriptionProvider parent)

: base(parent)

{

}

///

/// Create and return a custom type descriptor and chains it with the original

/// custom type descriptor

///

public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)

{

return new UIAttributeCustomTypeDescriptor(base.GetTypeDescriptor(objectType, instance));

}

}

public sealed class UIAttributeCustomTypeDescriptor : CustomTypeDescriptor where T : IUIAttributes

{

///

/// Constructor

///

public UIAttributeCustomTypeDescriptor(ICustomTypeDescriptor parent)

: base(parent)

{

}

public override AttributeCollection GetAttributes()

{

Type UIType = typeof(T).GetInterface(typeof(IUIAttributes).Name);

if (UIType != null)

{

IUIAttributes UIInstance = GetPropertyOwner(base.GetProperties().Cast<PropertyDescriptor>().First()) as IUIAttributes;

bool instanceLevelRoles = UIInstance.IsReadOnly();

List<Attribute> attributes = new List<Attribute>(base.GetAttributes().Cast<Attribute>());

UIAttributes UIAttrib = new UIAttributes(instanceLevelRoles);

TypeDescriptor.AddAttributes(UIInstance, UIAttrib);

attributes.Add(UIAttrib);

return new AttributeCollection(attributes.ToArray());

}

return base.GetAttributes();

}

///

/// This method add a new property to the original collection

///

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 UIType = typeof(T).GetInterface("IUIAttributes");

if (UIType != null)

{

foreach (PropertyDescriptor pd in originalProperties)

{

IUIAttributes UIInstance = GetPropertyOwner(pd) as IUIAttributes;

bool propertyIsReadOnly = UIInstance.IsReadOnly(pd.Name);

UIAttributes UIAttrib = new UIAttributes(propertyIsReadOnly);

// Create a new property and add it to the collection

PropertyDescriptor newProperty = TypeDescriptor.CreateProperty(typeof(T), pd.Name, pd.PropertyType, UIAttrib);

newProperties.Add(newProperty);

}

// Finally return the list

return new PropertyDescriptorCollection(newProperties.ToArray(), true);

}

return base.GetProperties();

}

}

I have overridden GetAttributes in which I query the underlying instance to get read-only mode of the instance. Similarly, I have also overridden GetProperties which will in turn query the instance to get read-only mode of the specified property name.

We are now ready to associate the UIAttribute with our class instances.

ProductViewData pvd = new ProductViewData();

pvd.ProductID = 1;

pvd.Name = "Product1";

pvd.Price = 99.99;

TypeDescriptor.AddProvider(new CustomTypeDescriptionProvider<ProductViewData>(TypeDescriptor.GetProvider(typeof(ProductViewData))),pvd);

And then by Reflection we can get properties attributes and check its layout behavior without knowing instance type, in next piece of code ‘Model’ is the instance name and ‘Name’ is the property name.

public bool IsPropReadOnly(string Name)

{

bool readonlyatt = false;

UIAttributes uiatt = TypeDescriptor.GetAttributes(Model).Cast<Attribute>().SingleOrDefault(a => a.GetType().Name == typeof(UIAttributes).Name) as UIAttributes;

if ((uiatt != null) && uiatt.IsReadOnly)

return uiatt.IsReadOnly;

PropertyInfo propInfo = Model.GetType().GetProperty(Name);

//(propInfo =>

{

PropertyDescriptor propDescriptor = TypeDescriptor.GetProperties(Model).Cast<PropertyDescriptor>().SingleOrDefault(p => propInfo.Name == p.Name);

if (propDescriptor != null)

{

UIAttributes attrib = propDescriptor.Attributes.Cast<Attribute>().SingleOrDefault(p => p.GetType().Name == typeof(UIAttributes).Name) as UIAttributes;

if (attrib != null)

{

readonlyatt = attrib.IsReadOnly;

}

}

}

//);

return readonlyatt;

}

Thanks for this Post it helps a lot.

Comments

 

Generic editable GridView – ASP.NET MVC « Mina Labib's Blog said:

Pingback from  Generic editable GridView &#8211; ASP.NET MVC &laquo; Mina Labib&#039;s Blog

November 6, 2009 5:09 PM

Leave a Comment

(required)  
(optional)
(required)  
Add
ASPXWizard.net some rights reserved 2005-2007
Powered by Community Server (Non-Commercial Edition), by Telligent Systems