Jan Tielens
Ordina Euregio NV
Applies to:
Visual Studio.NET 2003
Level: Intermediates - Advanced
Summary:
Attributes can be used to decorate language elements with additional information that can be retrieved at run time by using Reflection. You can easily build your own custom attributes to create powerful generic solutions. At first sight they might to seem a little bit useless. But this article shows how they can be used to create a simple extensible framework to provide validation of property values.
Downloads
CustomValidationSource
Contents
Attributes?
Custom Attributes
Why Use Attributes?
Building the ValidatorAttribute Base Class
Building Custom Validator Classes
Building the Validation Logic
Using the Validation
Conclusion
Further Reading
About the author
Attributes?
First of all, what are attributes? In the MSDN Library the following description can be found: The common language runtime allows you to add keyword-like descriptive declarations, called attributes, to annotate programming elements such as types, fields, methods, and properties. Attributes are saved with the metadata of a Microsoft .NET Framework file and can be used to describe your code to the runtime or to affect application behavior at run time. While the .NET Framework supplies many useful attributes, you can also design and deploy your own. (Extending Metadata Using Attributes)
So you can add attributes to programming elements, in which you can provide some extra information. This information can be retrieved from those programming elements at run time by using Reflection. For example, we can use the System.ComponentModel.DescriptionAttribute to provide some information about a the class:
Public Class Customer
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal Value As String)
_name = Value
End Set
End Property
End Class
As you can see, the Description attribute is attached to the Customer class. The value for the Description attribute is set to the String value between the parentheses. Notice that you can use DescriptionAttribute or Description as the name of the attribute; you can omit the Attribute suffix. Also notice that this attribute is attached to a class. Some attributes can attach to any programming element (property, class, parameter, .) while others can only attach to specific programming elements.
So what can you do with these attributes? You can retrieve the values of the attributes at run time using Reflection. Reflection can examine a type and retrieve all or some of the attributes.
Dim customerType As Type = GetType(Customer)
Dim descr As System.ComponentModel.DescriptionAttribute
For Each descr In customerType.GetCustomAttributes( _
GetType(System.ComponentModel.DescriptionAttribute), True)
MsgBox(descr.Description)
Next
The example shows that we first need to get the Type of the Customer class, which is achieved using the GetType function. Then the GetCustomAttributes function is used to get all the attributes of the type DescriptionAttribute. For each of these attributes, in the example only one, the Description is showed in a message box.

Custom Attributes
There are many attributes provided by the .NET Framework, but it gets really interesting when you build your own attributes. To do so, the Attribute class can be used to inherit from.
Public Class ExtendeDescriptionAttribute
Inherits System.Attribute
Private _description As String
Private _displayOrder As Integer
Public Sub New(ByVal description As String, ByVal displayOrder As Integer)
MyBase.New()
_description = description
_displayOrder = displayOrder
End Sub
Public Property Description() As String
Get
Return _description
End Get
Set(ByVal Value As String)
_description = Value
End Set
End Property
Public Property DisplayOrder() As Integer
Get
Return _displayOrder
End Get
Set(ByVal Value As Integer)
_displayOrder = Value
End Set
End Property
End Class
The example above shows a custom attribute class that has two properties: description and display order. As you can see the ExtendedDescriptionAttribute class inherits from the Attribute class provided in the .NET Framework. It's a guideline to use the "Attribute" suffix for classes that are custom attributes, so make sure always to use this suffix. The AttributeUsage attribute is attached to the class to specify for which type of programming elements the custom attribute can be used and if the attribute can be repeated. In this example the ExtendedDescriptionAttribute can be used for properties and can be repeated. Custom attributes can be retrieved at runtime using Reflection, like the first example. Also the use of the attribute does not differ from the attributes provided in the .NET Framework:
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal Value As String)
_name = Value
End Set
End Property

Why Use Attributes?
At first sight, the usage of attributes seems to be limited. Why would you provide information about the code you write, which can only be used at run time? By using custom attributes you can make more generic functionality. To illustrate the use of custom attributes, this article will describe how to make use of attributes to build a small framework to validate properties. The goal is to build a set of attributes that allows developers to easily add validation, with a minimum of code:
Validators.LengthValidator("Max length of name is 30.", MaxLength:=30)> _ Public Property Name() As String Get Return _name End Get Set(ByVal Value As String) _name = Value End Set End Property As you can see, two attributes are attached to the Name property. They will enforce two validation rules for that property: the name cannot be empty and the maximum length is 30 characters. These attributes will be the only thing you need to add to your class properties to have validation! Building the ValidatorAttribute Base Class The idea is to build a whole set of validator attributes to check various rules, for example length, maximum value, minimum value, . Since there is some basic functionality that every validator attribute should have, this functionality is encapsulated in an abstract base class. The Message property is used to store the message that should showed when the validation for a specific property failed. The UML diagram shows that ValidatorAttribute base class that is inherited by two validator classes that will be build later in this article. The ValidatorAttribute class inherits its base attribute functionality from the System.Attribute class that is provided by the .NET Framework. Building the ValidatorAttribute Base Class Public MustInherit Class ValidatorAttribute Inherits Attribute Private _message As String Public Property Message() As String Get Return _message End Get Set(ByVal Value As String) _message = Value End Set End Property Friend MustOverride Function IsValid(ByVal item As Object) As Boolean Public Sub New(ByVal message As String) _message = message End Sub End Class The ValidatorAttribute inherits from the System.Attribute class and can be attached multiple times to properties. Each implementation of a ValidatorAttribute has the Message property that contains the message that should be displayed if the validation of that property failed. The actual validation of the value of the property is done in the IsValid function. Because each implementation of the ValidatorAttribute class will have its own validation rules, only the definition of this member is given; all inherited classes must implement this function. Building Custom Validator Classes Now that the abstract base class is available, we can start building the implementations of this class. Each implementation will have its own validation rule. All the implementations inherit from the ValidatorAttribute class, so they'll all have the Message property for free. In the source code for this example, the custom validator implementations are placed in the Validators namespace. NotEmptyStringValidatorAttribute Class One of the most basic validations that can be done is to check the value of the property is not empty. Since this is only applicable to a String value, the validation will check for the String.Empty value. Public Class NotEmptyStringValidatorAttribute Inherits ValidatorAttribute Public Sub New(ByVal message As String) MyBase.New(message) End Sub Friend Overrides Function IsValid(ByVal item As Object) As Boolean If CType(item, String) = String.Empty Then Return False Else Return True End If End Function End Class In the IsValid function, the item parameter is cast from the Object type to the String type, to be able to compare with the String.Empty value. If the item equals the String.Empty value the validation failed, so False is returned by the IsValid function, otherwise True is returned indicating the validation succeeded. LengthValidatorAttribute Class String values can be of any length, but in a database, most of the time string values will have a maximum length. So a validation rule that is very often is used is the maximum length of a property. The code below shows how to build the LengthValidatorAttribute that can validate the minimum and maximum length of a string value. Public Class LengthValidatorAttribute Inherits ValidatorAttribute Private _maxLength As Integer Private _minLength As Integer Public Property MinLength() As Integer Get Return _minLength End Get Set(ByVal Value As Integer) _minLength = Value End Set End Property Public Property MaxLength() As Integer Get Return _maxLength End Get Set(ByVal Value As Integer) _maxLength = Value End Set End Property Public Sub New(ByVal message As String) MyBase.New(message) _maxLength = 0 'Default value _minLength = 0 'Default value End Sub Friend Overrides Function IsValid(ByVal item As Object) As Boolean If _maxLength > 0 Then 'If maxLength needs to be checked. If Not item Is Nothing Then 'It item is Nothing, don't check the length. If CType(item, String).Length > Me.MaxLength Then Return False End If End If End If If _minLength > 0 Then 'If minLength needs ot be checked. If Not item Is Nothing Then If CType(item, String).Length < Me.MinLength Then Return False End If Else 'If item is Nothing the validation will failed. Return False End If End If 'Otherwise the validation succeeded. Return True End Function End Class The LengthValidatorAttribute class has two additional properties: MaxLength and MinLength. These properties are used to store either the maximum length and/or minimum length of the property to which this attribute is attached. They can both be used alone, or in combination to each other. The IsValid function can be divided into two parts: the first checks for the maximum length of the property, the second part checks for the minimum length of the property, if applicable. As you can see both the MaxLength and MinLength values are set to zero in the constructor of the class. In fact this is not really necessary because Integer have always the default zero value, but doing so you indicate that somewhere in your class you rely on those default values. Building the Validation Logic ValidationException Class Now that the validation attributes that decorate the properties are ready, the building of component that actually does the validation needs to be done. But first of all, lets think about what should happen if the validation of one or more properties fails. In that case an exception should be raised. This exception should contain all the information about the properties that caused the validation to fail. The easy way to accomplish this is to put all this information into a string and throw an ApplicationException. But it's nicer to provide a custom exception not only containing the concatenated message, but also for each property for which the validation failed, the corresponding message separately. This gives you the advantage to either treat the validation exception as a whole, or treating the validation exception into detail for each property that caused the validation to fail; for example when you want to put a message next to the textboxes for the properties that have wrong values. Using a custom exception also enables you to write Catch statements specific for the ValidationException. The UML class diagram shows how the ValidationException will be implemented. For each property for which validation failed, will be a ValidationExceptionDetail object in the ValidationException. The ValidationExceptionDetail class has two properties to store the corresponding message and property name. ValidationException Class Public Class ValidationException Inherits ApplicationException Private _details As ValidationExceptionDetail() Public Sub New(ByVal message As String, ByVal details As ValidationExceptionDetail()) MyBase.New(message) _details = details End Sub Public ReadOnly Property Details() As ValidationExceptionDetail() Get Return _details End Get End Property End Class Public Class ValidationExceptionDetail Private _propertyName As String Private _message As String Public Sub New(ByVal propertyName As String, ByVal message As String) _propertyName = propertyName _message = message End Sub Public Sub New(ByVal message As String) Me.New(String.Empty, message) End Sub Public Property PropertyName() As String Get Return _propertyName End Get Set(ByVal Value As String) _propertyName = Value End Set End Property Public Property Message() As String Get Return _message End Get Set(ByVal Value As String) _message = Value End Set End Property End Class ValidationEngine Class The ValidationEngine class will have the logic to validate a class based on the attributes that are attached to it. As you can see in the code, there is only one public member; the Validate sub which takes an object as a parameter. This object is the object that will be validated. If the validation succeeds, nothing will happen, but if the validation fails, a ValidationException is throwed. Notice that the Validate member is Shared (static) so no instance of the ValidationEngine class is needed to use that member. The type of the object that is passed, is retrieved using the GetType function, which returns an instance of the type class for that object. The code shows that first all the properties are traversed in a for each loop, using the GetProperties function of the type class. Then for each property, all the custom attributes of the type ValidatorAttribute are fetched. Now for each ValidatorAttribute, the IsValid function is invoked. This function takes an object as a parameter that should be validated. So the actual value of the current property needs to be retrieved and passed in that parameter. This is accomplished by using some Reflection magic: by using the PropertyInfo object, the value of the corresponding property of the current item is retrieved. If the IsValid function returns False, so when the validation failed, a ValidationExceptionDetail object is put in the details ArrayList. When all the properties are validated, the details ArrayList is checked. If it contains no items, nothing should happen because all validations were successful. If one ore more items are in the ArrayList, an exception message is build by concatenating all the messages from the ValidationExceptionDetails. This message is used to construct a new ValidationException, to which the details ArrayList is added too. Imports System.Reflection Public Class ValidationEngine Private Sub New() End Sub Public Shared Sub Validate(ByVal item As Object) Dim message As String Dim details As New ArrayList For Each propInfo As PropertyInfo In item.GetType.GetProperties For Each attr As ValidatorAttribute _ In propInfo.GetCustomAttributes(GetType(ValidatorAttribute), True) If Not attr.IsValid(propInfo.GetValue(item, Nothing)) Then details.Add( _ New ValidationExceptionDetail(propInfo.Name, attr.Message)) End If Next Next If details.Count > 0 Then 'Construct message For Each detail As ValidationExceptionDetail In details If message <> String.Empty Then message += vbCrLf message += detail.Message Next Throw New ValidationException(message _ , details.ToArray(GetType(ValidationExceptionDetail))) End If End Sub End Class Using the Validation To illustrate how the validations can be used, a very simple business entity example is included; the Customer class with two properties: Name and Street. The Name property cannot be empty and both the Name and Street properties cannot contain more than 30 characters. Public Class Customer Private _name As String Private _street As String Validators.LengthValidator("Max length of name is 30.", MaxLength:=30)> _ Public Property Name() As String Get Return _name End Get Set(ByVal Value As String) _name = Value End Set End Property Public Property Street() As String Get Return _street End Get Set(ByVal Value As String) _street = Value End Set End Property End Class In the source code a small test application is included with a simple user interface to test the validation of a customer object. A new customer object is created and validated in a Try . Catch block where a ValidationException is catched. The Message property of this exception is used to display the information about the properties for which the validation failed. The Details property is used to change the back color of each Textbox causing the validation to fail. Validation Test 'Reset backcolors customerName.BackColor = Color.White customerStreet.BackColor = Color.White Dim c As New Customer c.Name = customerName.Text c.Street = customerStreet.Text Try ValidationEngine.Validate(c) validateResult.Text = "Validation succeeded!" Catch ex As ValidationException validateResult.Text = ex.Message For Each detail As ValidationExceptionDetail In ex.Details Select Case detail.PropertyName Case "Name" customerName.BackColor = Color.LightYellow Case "Street" customerStreet.BackColor = Color.LightYellow End Select Next End Try Conclusion Working with attributes in .NET is great. In combination with Reflection you can create generic and extensible solutions for common problems very easily. As you can see in the example described in this article, once the base functionality is build, extending it is very simple. So you can build more custom ValidatorAttributes to perform more validation rules, for example checking the minimum and maximum value of numeric properties, capitalization, dates, . just use your creativity! Further Reading Extending Metadata Using Attributes About the author Jan Tielens is a .NET Architect of Ordina Euregio NV. Already since the early betas of .NET he has spent a lot of time getting familiar with the details of the .NET Framework and the new possibilities and technologies. Now he's not only involved in .NET projects, but he's also evangelizing .NET among his colleagues by giving presentations and writing articles for MSDN Belux. You can contact him at jan.tielens@ordina-euregio.com or read his weblog at http://weblogs.asp.net/jan. The demo code is hosted by













http://www.microsoft.com/belux/nl/msdn/community/columns/jtielens/attributes.mspx

