Adding Custom Metadata to .NET Elements

The Reflection APIs work by querying the metadata stored in a .NET assembly. Custom attributes are a simple way to extend the metadata of any given managed element. Using custom attributes, you can add extra information to an assembly's metadata and then query for this extra information at runtime.

Defining a Custom Attribute

A custom attribute is a declarative programming construct that allows you to extend a language element's metadata. This information is stored in an assembly's metadata and can be retrieved at runtime. A corresponding attribute class must exist before an attribute can be used to decorate a language element. All attribute classes inherit from System.Attribute. The attribute class contains properties that store and retrieve the extra declared metadata. Listing 13.26 demonstrates how to define a custom attribute.

Listing 13.26.
C#
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Struct)]
class BusinessObjectAttribute : System.Attribute {
  private string m_DBName;
  private string m_TableName;
  private string m_QueryString;

  public BusinessObjectAttribute(string dbName,
                                 string tableName) {
    m_DBName = dbName;
    m_TableName = tableName;
  }

  public string Database{
    get{ return m_DBName; }
    set{ m_DBName = value; }
  }

  public string Table {
    get{ return m_TableName; }
    set{ m_TableName = value; }
  }

  public string QueryString {
    get{ return m_QueryString; }
    set{ m_QueryString = value; }
  }
}

VB
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Struct)> _
Class BusinessObjectAttribute
    Inherits System.Attribute

    Private m_DBName As String
    Private m_TableName As String
    Private m_QueryString As String

    Public Sub New(ByVal dbName As String, _
                   ByVal tableName As String)
        m_DBName = dbName
        m_TableName = tableName
    End Sub

    Public Property Database() As String
        Get
            Return m_DBName
        End Get
        Set(ByVal Value As String)
            m_DBName = Value
        End Set
    End Property

    Public Property Table() As String
        Get
            Return m_TableName
        End Get
        Set(ByVal Value As String)
            m_TableName = Value
        End Set
    End Property

    Public Property QueryString() As String
        Get
            Return m_QueryString
        End Get
        Set(ByVal Value As String)
            m_QueryString = Value
        End Set
    End Property
End Class

The code declares a new Attribute named BusinessObjectAttribute. This attribute is intended to be applied to classes or structs. Astute readers will notice that the attribute class itself is in turn decorated with an attribute, AttributeUsage. The AttributeUsage attribute describes how a custom attribute can be used. The AttributeUsage has three properties: the required AttributeTarget property, the optional AllowMultiple property, and the optional Inherited property.

The AttributeTargets property specifies the language elements on which the attribute can be applied. The values of the AttributeTargets enumeration can be combined to specify multiple targets. In the previous example, the BusinessObjectAttribute can be applied to classes and structs. Table 13.12 shows all the possible AttributeTargets values.

Table 13.12. AttributeTargets Enumeration Value
MEMBER
All
Assembly
Class Module
Constructor
Delegate Struct
Enum
Field
Interface
Method
Module
Parameter
Event
Property
ReturnValue
Struct

CLARIFY WHAT LANGUAGE ELEMENT AN Attribute APPLIES TO

Usually, the attribute will directly precede the language element to which it applies. However, position of the attribute is not always enough to determine to which element the attribute applies. For instance, consider this snippet:

C#
[Attribute()]
public int Function(int) {...}

In this instance, there is no way to tell whether the attribute is intended for the method element or for the method element's return value. To clarify which element the attribute applies to, you prefix the attribute name with the AttributeTargets enumeration value that describes which language element to which it applies.

C#
[returnvalue:Attribute]
public int Function(int) {...}


The AllowMultiple property specifies whether the attribute can be used more than once on the same language element. This value is option and false by default. In the preceding example, the BusinessObjectAttribute can appear only once on the same class or struct.

The inherited property specifies whether the attribute is inherited by derived classes. This value is optional and is false by default. In the example the BusinessObjectAttribute will not be inherited by classes that derive from a class that is decorated with this attribute.

All custom attributes must inherit from the System.Attribute class. Also, attributes should use the Attribute suffix in their names. When the attribute is used, the Attribute suffix does not need to be included. Instead, the class name minus the Attribute suffix becomes an alias for the class. Listing 13.27 demonstrates how the BusinessObjectAttribute class can be applied to a class.

Listing 13.27.
C#
[BusinessObject("CustomerRecordsDB", "Customers")]
class Customer {
}

VB
<BusinessObject("CustomerRecordsDB", "Customers")> _
Public Class Customer
End Class

Here the BusinessObject name is actually an alias for the BusinessObjectAttribute class. It is no mistake that the snippet's attribute declaration appears similar to a construction call. The two parameters correspond to the two parameters to the attribute class's constructor. The parameters must appear in the same order as the parameters in the constructor declaration. It is also possible to specify properties that do not appear in the constructor's parameter list. Listing 13.28 demonstrates how to initialize the QueryString property of the BusinessObjectAttribute when it is applied to a class.

Listing 13.28.
C#
[BusinessObject("CustomerRecordsDB",
                "Customers",
                QueryString="SELECT * FROM CUSTOMERS")]
class Customers {
}

VB
<BusinessObject("CustomerRecordsDB", _
    "Customers", _
    QueryString="SELECT * FROM CUSTOMERS")> _
Public Class Customer
End Class

This code demonstrates how to use a named parameter to initialize a property that does not appear in the attributes constructor. The name of the named parameter corresponds to the property's accessor name, not the field name.

Retrieving Custom Attributes

Now that you have defined a custom attribute and applied the attribute to a language element, it is time to retrieve that attribute at runtime. You can retrieve custom attributes by using the GetCustomAttributes methods of the System.MemberInfo class. This method comes in these two flavors:

object [] GetCustomAttributes(bool)
object [] GetCustomAttributes(Type, bool)

To retrieve all of the custom attributes of a class, you can use the GetCustomAttributes method that takes one bool parameter. This bool parameter specifies whether to search the member's inheritance chain to find the attributes. This method returns either an array of all the custom attributes or an array of zero elements if no attributes are defined. Listing 13.29 demonstrates how to use the GetCustomAttributes method.

Listing 13.29.
C#
[BusinessObject("CustomerRecordsDB", "Customers")]
class Customer {
}

class CustomAttributeTest {
  public static void Main() {
    Type customerType = typeof(Customer);
    Object[] atts = customerType.GetCustomAttributes(false);
    foreach(Attribute att in atts) {
      if(att is BusinessObjectAttribute) {
        BusinessObjectAttribute b = (BusinessObjectAttribute)att;
          MessageBox.Show("Database: " + b.Database +
                            "
Table: " + b.Table);
        }
    }
  }
}

VB
Module Module1
    <BusinessObject("CustomerRecordsDB", "Customers")> _
    Public Class Customer
    End Class

    Sub Main()
        Dim cust As New Customer()
        Dim customerType = cust.GetType()
        Dim atts() = customerType.GetCustomAttributes(False)
        Dim i As Int32

        For i = 0 To atts.Length - 1
            If TypeOf atts(i) Is BusinessObjectAttribute Then
                Dim b = CType(atts(i), BusinessObjectAttribute)
                MessageBox.Show("Database: " & b.Database & _
                       Chr(13) & "Table: " + b.Table)
            End If
        Next i
    End Sub
End Module

To retrieve all of the custom attributes of a class that can be assigned to a given type, use the GetCustomAttributes method that takes two parameters. The first parameter is the type of the attribute for which to search. Only attributes that are assignable to this type will be returned. The second parameter specifies whether to search the member's inheritance chain to find the attribute. This parameter is identical to the sole parameter of the other GetCustomAttributes method overload. This method returns either an array of all the custom attributes or, if no attributes that are assignable to the specified type are defined, an array of zero elements. Listing 13.30 demonstrates how to use GetCustomAttributes to retrieve only BusinessObjectAttribute attributes.

Listing 13.30.
C#
[BusinessObject("CustomerRecordsDB", "Customers")]
class Customer {
}

class CustomAttributeTest {
  public static void Main() {
    Type customerType = typeof(Customer);
    Object[] atts = customerType.GetCustomAttributes(
        typeof(BusinessObjectAttribute), false);

    foreach(BusinessObjectAttribute att in atts) {
      MessageBox.Show("Database: " + att.Database +
                        "
Table: " + att.Table);
    }
  }
}

VB
Module Module1
  <BusinessObject("CustomerRecordsDB", "Customers")> _
  Public Class Customer
  End Class

  Sub Main()
    Dim boa = New BusinessObjectAttribute(String.Empty, String.Empty)
    Dim cust As New Customer()
    Dim customerType = cust.GetType()
    Dim atts() = customerType.GetCustomAttributes(boa.GetType(), False)
    Dim i As Int32

    For i = 0 To atts.Length - 1
      If TypeOf atts(i) Is BusinessObjectAttribute Then
        Dim b = CType(atts(i), BusinessObjectAttribute)
        MessageBox.Show("Database: " & b.Database & _
                        Chr(13) & "Table: " + b.Table)
      End If
    Next i
  End Sub
End Module

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.109.234