PostSharp 4.3 / / Post­Sharp Documentation / Custom Patterns / Developing Custom Aspects / Developing Simple Aspects / Introducing Custom Attributes
Introducing Custom Attributes

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.

This topic contains the following sections:

  • Introducing new custom attributes
  • Copying existing custom attributes
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.

  1. 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:

    C#
    public
                                   
                                  sealed
                                   
                                  class
                                   AutoDataContractAttribute : TypeLevelAspect, IAspectProvider
    {
        
                                  public
                                   IEnumerable<AspectInstance> ProvideAspects(
                                  object
                                   targetElement)
        {
    }
                                
  2. Implement the ProvideAspects(Object) method to cast the targetElement parameter to a Type 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, the Type 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 the Type containing a new DataContractAttribute and then iterates through each property of the Type returning a new AspectInstance with the DataMemberAttribute for each. Note that both the DataContractAttribute and DataMemberAttribute are both wrapped in CustomAttributeIntroductionAspect objects:

    C#
    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 Note

    Since the ProvideAspects(Object) method returns an IEnumerable, the yield keyword should be used to return aspects for PostSharp to apply.

  3. Apply the AutoDataContractAttribute class. In the following example we apply it to a Product class where it will decorate Product with DataContractAttribute and each member with DataMemberAttribute:

    C#
    [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:

C#
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:

  1. Create a TypeLevelAspect which implements IAspectProvider. In the snippet below our class is called CopyCustomAttributesFrom:

    C#
    class
                                   CopyCustomAttributesFrom : TypeLevelAspect, IAspectProvider
    {
    }
                                
  2. 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:

    C#
    class
                                   CopyCustomAttributesFrom : TypeLevelAspect, IAspectProvider
    {
                                  
    
                                  private
                                   Type sourceType;
                                  
    
    
                                  public
                                   CopyCustomAttributesFrom(Type srcType)
    {
        sourceType = srcType;
    }
        }
                                
  3. Implement ProvideAspects(Object):

    C#
    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.

  4. Decorate the ProductViewModel class with the CopyCustomAttributesFrom attribute, specifying Product as the source type in the constructor. During compilation, CopyCustomAttributesFrom’s ProvideAspects(Object) method will then perform the copy process from Product to ProductViewModel:

    C#
    [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:

Attribute Intro
Note Note

It is not possible to delete or replace an existing custom attribute.

See Also