Understanding .NET Attributes

Introduction

.NET assemblies are said to be self-describing. That means the information about an assembly is stored in the assembly itself. This information is called Metadata. Moreover, .NET allows you to put additional information in the metadata through Attributes. Attributes are used in many places within the .NET framework. Some examples of attributes are [WebMethod], [ServiceContract], and several data annotation attributes such as [Required] and [StringLength]. This article discusses what attributes are, how to use inbuilt attributes and how to create custom attributes.

What are .NET Attributes?

A .NET attribute is a piece of information that you can attach with an assembly, class, property, method and other members. An attribute contributes to the metadata about an entity under consideration. An attribute is basically a class that is either inbuilt or developer defined. Once created, an attribute can be applied to various targets including the ones mentioned above. To understand this better, let's take an example. Consider the following piece of code:

 [WebService]
public class WebService1 : System.Web.Services.WebService
{
  [WebMethod]
  public string HelloWorld()
  {
    return "Hello World";
  }
}

The above code represents a class named WebService1 that contains a method - HelloWorld(). Notice the class and method declaration. The WebService1 class is decorated with [WebService] and HelloWorld() is decorated with [WebMethod]. Both of them - [WebService] and [WebMethod] - are attributes. The [WebService] attribute indicates that the target of the attribute (WebService1 class) is a web service. Similarly, the [WebMethod] attribute indicates that the target under consideration (HelloWorld() method) is a web callable method. Although we won't go into the details of these specific attributes, it is sufficient to know that they are inbuilt attributes and contribute to the metadata of the respective entities.

Attributes can be broadly classified into two types - inbuilt and custom. The inbuilt attributes are provided by .NET framework and you can readily use them whenever required. Custom attributes are developer defined classes. You will learn to create custom attributes later in this article.

If you observe a newly created project in Visual Studio, you will find a file named AssemblyInfo.cs. This file houses attributes that are applicable to the entire project assembly. For example, consider the following AssemblyInfo.cs from an ASP.NET Web Application.

// General Information about an assembly is controlled through the following 
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("CustomAttributeDemo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CustomAttributeDemo")]
[assembly: AssemblyCopyright("Copyright ©  2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version 
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Revision and Build Numbers 
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

As you can see there are many attributes such as AssemblyTitle, AssemblyDescription and AssemblyVersion. Since their target is the underlying assembly, they use [assembly: <attribute_name>] syntax.

Information about the attributes can be obtained through reflection. Most of the inbuilt attributes are handled by .NET framework internally but for custom attributes you may need to devise a mechanism to observe the metadata emitted by them. You will see an example of this in later sections of this article.

Using Inbuilt Attributes

Now that you have some understanding of .NET attributes, let's see a more real world case where attributes are used. In this section you will use Data Annotation Attributes to validate model data. Begin by creating a new ASP.NET MVC Web Application. Then add a class to the Models folder and name it as Customer. The Customer class contains just two public properties and is shown below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace CustomAttributesMVCDemo.Models
{
    public class Customer
    {
        [Required]
        public string CustomerID { get; set; }

        [Required]
        [StringLength(20,MinimumLength=5,ErrorMessage="Invalid company name!")]
        public string CompanyName { get; set; }
    }
}

Notice the above code carefully. The Customer class consists of two string properties - CustomerID and CompanyName. What is more important for us is that these properties are decorated with data annotation attributes residing in the System.ComponentModel.DataAnnotations namespace. The [Required] attribute indicates that the CustomerID property must be assigned some value in order to consider it to be a valid model. Similarly, the CompanyName property is decorated with [Required] and [StringLength] attributes. The [StringLength] attribute is used to specify that the CompanyName should be a minimum five characters long and maximum twenty characters long. An error message is also specified in case the value assigned to the CompanyName property doesn't meet the criteria. Note that [Required] and [StringLength] attributes are actually classes - RequiredAttribute and StringLengthAttribute - defined in the System.ComponentModel.DataAnnotations namespace. The MinimumLength and ErrorMessage are the properties of StringLength class and they are set as shown above.

Now, add the HomeController class to the Controllers folder and write the following action methods:

public ActionResult Index()
{
    return View();
}

public ActionResult ProcessForm()
{
    Customer obj = new Customer();
    bool flag = TryUpdateModel(obj);
    if(flag)
    {
        ViewBag.Message = "Customer data received successfully!";
    }
    else
    {
        ViewBag.Message = "Customer data validation failed!";
    }
    return View("Index");
}

The Index() action method simply returns the Index view. The Index.cshtml consists of a simple <form> as shown below:

<form action="/home/processform" method="post">
    <span>Customer ID : </span>
    <input type="text" name="customerid" />
    <span>Company Name : </span>
    <input type="text" name="companyname" />
    <input type="submit" value="Submit" />
</form>
<strong>@ViewBag.Message</strong>
<strong>@Html.ValidationSummary()</strong>

The values entered in the customerid textbox and companyname textbox are submitted to the ProcessForm() action method.

The ProcesssForm() action method uses TryUpdateModel() method to assign the properties of the Customer model with the form field values. The TryUpdateModel() method returns true if the model properties contain valid data as per the condition set by the data annotation attributes, otherwise it returns false. Accordingly, the Message property of the ViewBag is set to a message. The model errors are displayed on the page using ValidationSummary() Html helper.

Now, run the application and try submitting the form without entering CompanyName value. The following figure shows how the error message is displayed:

Validation Error Message
Validation Error Message

Thus data annotation attributes are used by ASP.NET MVC to perform model validations. 

Creating Custom Attributes

In the preceding example, you used some inbuilt attributes. In this section you will create a custom attribute and then use it in an application. For the sake of this example let's assume that you have developed a class library that contains some complex business processing. You want that this class library should be consumed by only those applications that have a valid license key supplied by you. You can devise a simple technique to accomplish this task.

Note:
Remember that we are developing this example purely for the sake of demonstrating the usage of custom attributes. In a real world case you may resort to a more robust and professional licensing system.

Begin by creating a new class library project. Name the project LicenseKeyAttributeLib. Modify the default class from the class library to resemble the following code:

namespace LicenseKeyAttributeLib
{
    [AttributeUsage(AttributeTargets.All)]
    public class MyLicenseAttribute:Attribute
    {
        public string Key { get; set; }
    }
}

As you can see the MyLicenseAttribute class is created by inheriting it from the Attribute base class. The Attribute base class is provided by the .NET framework. By convention all the attribute classes end with "Attribute". However, while using these attributes you don't need to specify "Attribute". Thus MyLicenseAttribute class will be used as [MyLicense] on the target. The MyLicenseAttribute class contains just one property - Key - that represents a license key.

The MyLicenseAttribute itself is decorated with an inbuilt attribute - [AttributeUsage]. The [AttributeUsage] attribute is used to set the target for the custom attribute being created. A value of AttributeTargets.All means that MyLicenseAttribute can be applied to any entity (assembly, class, method etc.). You can use various members of the AttributeTargets enumeration to narrow down the target of the custom attribute.

Now, add another class library to the same solution and name it ComplexClassLib. The ComplexClassLib represents the class library doing some complex task and consists of a class as shown below:

public class ComplexClass1
{
    public string ComplexMethodRequiringKey()
    {
        //some code goes here
        return "Hello World!";
    }
}

The ComplexMethodRequiringKey() method of the ComplexClass1 is supposed to be doing some complex operation. You will revisit this method shortly. 

Using Custom Attributes

Next, add an ASP.NET Web Forms Application to the same solution. Refer both - LicenseKeyAttributeLib and ComplexClassLib - assemblies in the web application. Then invoke the ComplexMethodRequiringKey() inside the Page_Load event handler:

protected void Page_Load(object sender, EventArgs e)
{
  ComplexClassLib.ComplexClass1 obj = new ComplexClassLib.ComplexClass1();
  Label1.Text = obj.ComplexMethodRequiringKey();
}

The return value from ComplexMethodRequiringKey() is assigned to a Label control.

Now it's time to use the MyLicenseAttribute class you created earlier. Open the AssemblyInfo.cs file from the web application and add the following code to it:

using System.Reflection;
...
using LicenseKeyAttributeLib;

...

[assembly: MyLicense(Key = "4fe29aba")]

The AssemblyInfo.cs file now uses MyLicense custom attribute to specify a license key. Notice that although the attribute class name is MyLicenseAttribute, while using the attribute you just mention it as MyLicense. 

Obtaining Attribute Information Using Reflection

So far so good. You created a custom attribute and you also used it in a web application. But what's the real use of this attribute? Where exactly the license key is being validated?

That's the last piece of code you need to glue in this example. Open the ComplexClass1 from ComplexClassLib project again. Refer LicenseKeyAttributeLib assembly inside the ComplexClassLib project. Then modify the ComplexMethodRequiringKey() method as follows:

public string ComplexMethodRequiringKey()
{
  Assembly a = Assembly.GetCallingAssembly();
  MyLicenseAttribute attb = a.GetCustomAttribute<MyLicenseAttribute>();
  if(attb==null)
  {
    throw new Exception("You don't have a license key!");
  }
  else
  {
    //validate license key here
    //throw exception if invalid
  }
  return "Your license key is " + attb.Key;
}

The ComplexMethodRequiringKey() method is now modified to read the MyLicense attribute. This is done using reflection. The GetCallingAssembly() method of the Assembly class returns the Assembly that is currently calling the ComplexMethodRequiringKey() method. The code then uses GetCustomAttribute() method to retrieve the MyLicenseAttribute information. If the calling assembly (web application in this case) doesn't contain [MyLicense] attribute then GetCustomAttribute() will return null. If so the code throws an exception. If [MyLicense] attribute is applied to the calling assembly its license key can be retrieved using the Key property of the MyLicenseAttribute class. Although the above code doesn't validate the license key as such you can add such a logic as per your requirement (see the comment placeholder in the code).

Now run the web application and you should see the license key being outputted in the browser. Then remove the [MyLicense] attribute from the AssemblyInfo.cs file and run the application again. This time you will get an exception - "You don't have a license key!".

You Don't Have a License Key
You Don't Have a License Key

Summary

Attributes allow you to add metadata to their target. The .NET framework provides several inbuilt attributes and you can also create your own. This article introduced you to .NET attributes. You learned to use inbuilt data annotation attributes to validate model data. You also created a custom attribute and used it to decorate an assembly. A custom attribute is a class usually derived from the Attribute base class. You can add members to the custom attribute class just like you do for any other class. You can also specify a target for the attribute class using [AttributeUsage] attribute and AttributeTargets enumeration.



Related Articles

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds