Applying custom attributes to class members in C# is a powerful way to add metadata about those members at compile time.
PostSharp provides the ability to create a custom attribute class which when applied to another class, can iterate through those class members and automatically decorate them with custom attributes. This can be useful for example, to automatically apply custom attributes or groups of custom attributes when new class members are added, without having to remember to do it manually each time.
Introducing new custom attributes
In the following example, we’ll create an attribute decorator class which applies .NET’s DataContractAttribute to a class and DataMemberAttribute to members of a class at build time.
Start by creating a class called
AutoDataContractAttribute
which derives from TypeLevelAspect. TypeLevelAspect transforms the class into an attribute which can be applied to other classes. Also implement IAspectProvider which exposes the ProvideAspects(object) method for iterating on class members. ProvideAspects(object) will be called for each member in the target class and will contain the code for applying the attributes:public sealed class AutoDataContractAttribute : TypeLevelAspect, IAspectProvider { public IEnumerable<AspectInstance> ProvideAspects(object targetElement) { }
Implement the ProvideAspects(object) method to cast the
targetElement
parameter to aType
object. Note that this method will be called at build time. Since ProvideAspects(object) will be called for the class itself and for each member of the target class, theType
object can be used for inspecting each member and making decisions about when and how to apply custom attributes. In the following snippet, the implementation returns a new AspectInstance for theType
containing a new DataContractAttribute and then iterates through each property of theType
returning a new AspectInstance with the DataMemberAttribute for each. Note that both the DataContractAttribute and DataMemberAttribute are both wrapped in CustomAttributeIntroductionAspect objects:public sealed class AutoDataContractAttribute : TypeLevelAspect, IAspectProvider { // This method is called at build time and should just provide other aspects. public IEnumerable<AspectInstance> ProvideAspects(object targetElement) { Type targetType = (Type) targetElement; CustomAttributeIntroductionAspect introduceDataContractAspect = new CustomAttributeIntroductionAspect( new ObjectConstruction(typeof (DataContractAttribute).GetConstructor(Type.EmptyTypes))); CustomAttributeIntroductionAspect introduceDataMemberAspect = new CustomAttributeIntroductionAspect( new ObjectConstruction(typeof (DataMemberAttribute).GetConstructor(Type.EmptyTypes))); // Add the DataContract attribute to the type. yield return new AspectInstance(targetType, introduceDataContractAspect); // Add a DataMember attribute to every relevant property. foreach (PropertyInfo property in targetType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance)) { if (property.CanWrite) yield return new AspectInstance(property, introduceDataMemberAspect); } } }
Note
Since the
ProvideAspects
method returns anIEnumerable
, the yield keyword should be used to return aspects for PostSharp to apply.Apply the
AutoDataContractAttribute
class. In the following example we apply it to aProduct
class where it will decorateProduct
with DataContractAttribute and each member with DataMemberAttribute:[AutoDataContractAttribute] public class Product { public int ID { get; set; } public string Name { get; set; } public int RevisionNumber { get; set; } }
Copying existing custom attributes
Another way to introduce attributes to class members is to copy them from another class. This is useful, for example, when distinct classes have members with the same names and are of the same types. In this case, attributes can be defined in one class and then that class can be used to decorate other similar classes with same attributes.
In the following snippet, Product
’s ID
and Name
properties have both been modified to contain an additional attribute from the System.ComponentModel.DataAnnotations
namespace – Editable
, Display
, and Required
respectively. Below Product
is another class called ProductViewModel
containing the same properties to which we want to copy the attributes to:
class Product
{
[EditableAttribute(false)]
[Required]
public int Id { get; set; }
[Display(Name = "The product's name")]
[Required]
public string Name { get; set; }
public int RevisionNumber { get; set; }
}
class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public int RevisionNumber { get; set; }
}
To copy the attributes from the properties of Product
to the corresponding properties of ProductViewModel
, create an attribute class which can be applied to ProductViewModel
to perform this copy process:
Create a TypeLevelAspect which implements IAspectProvider. In the snippet below our class is called
CopyCustomAttributesFrom
:class CopyCustomAttributesFrom : TypeLevelAspect, IAspectProvider { }
Create a constructor to take in the class type from which the property attributes are to be copied from. This class type will be used in the next step to enumerate its properties:
class CopyCustomAttributesFrom : TypeLevelAspect, IAspectProvider { private Type sourceType; public CopyCustomAttributesFrom(Type srcType) { sourceType = srcType; } }
Implement ProvideAspects(object):
class CopyCustomAttributesFrom : TypeLevelAspect, IAspectProvider { // Details skipped. public IEnumerable<AspectInstance> ProvideAspects(object targetElement) { Type targetClassType = (Type)targetElement; //loop thru each property in target foreach (PropertyInfo targetPropertyInfo in targetClassType.GetProperties()) { PropertyInfo sourcePropertyInfo = sourceType.GetProperty(targetPropertyInfo.Name); //loop thru all custom attributes for the source property and copy to the target property foreach (CustomAttributeData customAttributeData in sourcePropertyInfo.GetCustomAttributesData()) { //filter out attributes that aren’t DataAnnotations if (customAttributeData.AttributeType.Namespace.Equals("System.ComponentModel.DataAnnotations")) { CustomAttributeIntroductionAspect customAttributeIntroductionAspect = new CustomAttributeIntroductionAspect(new ObjectConstruction(customAttributeData)); yield return new AspectInstance(targetPropertyInfo, customAttributeIntroductionAspect); } } } } }
The ProvideAspects(object) method iterates through each property of the target class and then gets the corresponding property from the source class. It then iterates through all custom attributes defined for the source property, copying each to the corresponding property of the target class. ProvideAspects(object) also filters out attributes which aren’t from the
System.ComponentModel.DataAnnotations
namespace to demonstrate how you may want to ignore some attributes during the copy process.Decorate the
ProductViewModel
class with theCopyCustomAttributesFrom
attribute, specifyingProduct
as the source type in the constructor. During compilation,CopyCustomAttributesFrom
’s ProvideAspects(object) method will then perform the copy process fromProduct
toProductViewModel
:[CopyCustomAttributesFrom(typeof(Product))] class ProductViewModel { // Details skipped. }
The following screenshot shows the Product
and ProductViewModel
classes reflected from an assembly. Here we can see that the Editable
and Display
attributes were copied from Product
to ProductViewModel
using CopyCustomAttributesAttribute at build time:
Note
It is not possible to delete or replace an existing custom attribute.
See Also
Reference
ProvideAspects(object)
CopyCustomAttributesAttribute
DataContractAttribute
DataMemberAttribute
TypeLevelAspect
IAspectProvider
AspectInstance
CustomAttributeIntroductionAspect