Using Attributes to Improve the Quality and Usability of Your Code Part 1 - Decorating Business Objects for Data Binding

I am a big fan of attributes in .NET. Ever since I discovered them in .NET 1, I have been digging in the MSDN documentation to find more useful attributes that I can use to improve the quality of the code I write and design. With the release of .NET 2, the number of attributes has increased quite a bit. During my search I discovered a number of useful attributes that have helped me write better code so I thought it was time for a short article series about attributes. This installment, Part 1 - Decorating Business Objects for Data Binding talks about applying attributes to business objects to influence the way these objects are used and seen in data binding scenario's. In a future installment I'll discuss attributes that can be used to improve the debugging experience in the Visual Studio 2005 IDE.

Read Part 2 - Improving Debugging

What is an Attribute Anyway?

An attribute is a bit of metadata that you can attach to elements like classes, methods, fields and assemblies. They allow you to provide information about your element that can be used at design time or at run-time. Here's what the MSDN has to say about attributes:

The Attribute class associates predefined system information or user-defined custom information with a target element. A target element can be an assembly, class, constructor, delegate, enum, event, field, interface, method, portable executable file module, parameter, property, return value, struct, or another attribute.

Information provided by an attribute is also known as metadata. Metadata can be examined at run time by your application to control how your program processes data, or before run time by external tools to control how your application itself is processed or maintained. For example, the .NET Framework predefines and uses attribute types to control run-time behavior, and some programming languages use attribute types to represent language features not directly supported by the .NET Framework common type system.

In it's simplest form, an attribute looks like this:

VB.NET
<WebMethod()> _
Public Function HelloWorld() As String
  Return "Hello World"
End Function
	
C#
[WebMethod]
public string HelloWorld()
{
  return "Hello World";
}		

The attribute WebMethod, indicated by <> in Visual Basic and [] in C# is used to mark this method as a web method, accessible through a web service.

However, there are many more attributes available and you can even create your own. In the remainder of this article, I'll show you how to apply attributes to a custom Business Object that can be used in data binding scenario's in ASP.NET. By applying the right attributes to objects, methods and properties, you can greatly enhance a developer's productivity, because the Visual Web Developer IDE can make intelligent decisions based on the class's attributes.

As an example, I'll use a modified version of the Bug class from chapter 12 - The Wrox BugBase from my book ASP.NET 2.0 Instant Results.

An Introduction to the Bug Class

To understand what the Bug class is, and how and where you can apply attributes, take a look at its class diagram. If you have read the book ASP.NET 2.0 Instant Results, you'll notice this is a slightly different version. For the purpose of this article, I limited the list of Members so the class is a bit easier to understand. I also renamed and moved a number of methods from the BugManager class into the Bug class itself that is shown in the following figure:

Bug Class Diagram

Figure 1 - The Bug Class

Besides a number of (public) properties, you also see a number of methods. These methods have a 1 to 1 mapping to the basic CRUD operations: Create, Read, Update and Delete. GetItem and GetList are both used to read; the first returns a single Bug instance, while GetList returns a generic List <Bug> or List (Of Bug), depending whether you prefer to think in C# or in VB.NET.

Applying Attributes to Classes

Now, this class is ready to be consumed by a web application. All you need to do is drop a GridView on the page, hook it up to an ObjectDataSource control by choosing Choose Data Source on the GridView's Smart Task panel, set a number of properties and you're ready to select, update and delete items. (Note: since the GridView doesn't support inserting, you need to use a DetailsView or FormView control for inserts). When you enter the Configure Data Source wizard, you'll see something like this:

The Configure Data Source Wizard

Figure 2 - The Configure Data Source Wizard

The list with classes in the business object drop-down can grow pretty large, depending on the number of classes in your project. Wouldn't it be great if you could limited the list to just those that provide data binding capabilities to the ObjectDataSource control? Sure, it would, but as soon as you check Show only data components, the list goes blank. Here's where Attributes come into play. To make your class show up in this list, so you can easily select it, you need to apply the DataObjectAttribute that you find in the System.ComponentModel namespace. To do that, you need to add a using / Imports statement for this namespace to your class, and then add the following attribute to the Bug class:

VB.NET
<DataObjectAttribute()> _
Public Class Bug
  ' Implementation here
End Class
	
C#
[DataObjectAttribute()]
public class Bug
{
 // Implementation here
}		

With this attribute applied, the class will now be seen as a business object, and show up correctly in the list with business objects in the Configure Data Source wizard (Note: you may need to recompile the application before VWD sees the new attributes):

Data Source Wizard with the Bug Class

Figure 3 - The Configure Data Source Wizard with the Bug Class

Applying Attributes to Methods

This is a nice start, but I always want more. If you click Next on the Wizard, you get a dialog that allows you to choose the Select, Update, Insert and Delete methods. While the IDE is smart enough to recognize the methods, it doesn't preselect them in the list. You have to manually select each method, on all four tabs (assuming you want to enable all four kinds of operations).

Data Source Wizard Select Method

Figure 4 - The Configure Data Source Wizard - Choosing the Select method manually

Fortunately, you can teach the IDE what methods it should select on all four tabs. You can do this by applying the DataObjectMethod attribute to the appropriate methods. One of the overloaded constructors for this attributes accepts two arguments: the first being a DataObjectMethodType that allows you to indicate whether the method is used to Delete, Insert, Select, Fill or Update an item. The second attribute, a boolean, is used to indicate whether a certain method is the default for its type of operation. So, an attribute that marks a method as the default Select method, could look like this:

VB.NET
<DataObjectMethod(DataObjectMethodType.Select, True)>
Public Shared Function GetList() As List(Of Bug)
  Throw New NotImplementedException()
End Function
	
C#
[DataObjectMethod(DataObjectMethodType.Select, true)]
public static List<Bug> GetList()
{
  throw new NotImplementedException();
}      

This defines the GetList method that returns List<Bug> as the default Select method. Below you see the relevant attributes applied to each method in the Bug class:

VB.NET
<DataObjectMethod(DataObjectMethodType.Select, True)> _
Public Shared Function GetList() As List(Of Bug)
  Throw New NotImplementedException()
End Function

<DataObjectMethod(DataObjectMethodType.Select, False)> _
Public Shared Function GetItem(ByVal id As Integer) As Bug
  Throw New NotImplementedException()
End Function

<DataObjectMethod(DataObjectMethodType.Insert, True)> _
Public Shared Function InsertItem(ByVal theBug As Bug) As Boolean
  Throw New NotImplementedException()
End Function

<DataObjectMethod(DataObjectMethodType.Update, True)> _
Public Shared Function UpdateItem(ByVal theBug As Bug) As Boolean
  Throw New NotImplementedException()
End Function

<DataObjectMethod(DataObjectMethodType.Delete, True)> _
Public Shared Function DeleteItem(ByVal theBug As Bug) As Boolean
  Throw New NotImplementedException()
End Function
	
C#
[DataObjectMethod(DataObjectMethodType.Select, true)]
public static List GetList()
{
  throw new NotImplementedException();
}

[DataObjectMethod(DataObjectMethodType.Select, false)]
public static Bug GetItem(int id)
{
  throw new NotImplementedException();
}

[DataObjectMethod(DataObjectMethodType.Insert, true)]
public static bool InsertItem(Bug theBug)
{
  throw new NotImplementedException();
}

[DataObjectMethod(DataObjectMethodType.Update, true)]
public static bool UpdateItem(Bug theBug)
{
  throw new NotImplementedException();
}

[DataObjectMethod(DataObjectMethodType.Delete, true)]
public static bool DeleteItem(Bug theBug)
{
  throw new NotImplementedException();
}

Notice how for the GetItem method the DataObjectMethod attribute has been applied as well, with its type set to Select. However, the parameter for isDefault has been set to false. This way, the other Select method is considered the default and will be preselected in the ObjectDataSource wizard:

The Configure Data Source Wizard with the Select method preselected

Figure 5 - The Configure Data Source Wizard - The Select method has been preselected

Just as with the select method, the Update, Insert and Delete methods have been preselected as well. This saves you from a lot of clicks if you need to configure many ObjectDataSource controls. You also make it easier for other developers using your business objects to determine what method to choose. Instead of figuring out what method to use, the most likely candidates have already been preselected.

Applying Attributes to Properties

Once I understood how to apply and use these attributes, I wanted even more. With the current setup, creating a page with a GridView showing records that can be sorted, paged, deleted and edited has now become a matter of a few clicks; you add a GridView, point it to an ObjectDataSource, and that's about it. However, for the GridView (and the FormView and DetailsView) to function correctly, it also needs the data key name(s) set to the object's property that represents the primary key in the data source. Normally, you would set this property manually on the GridView's property grid:

Property Grid for the Grid View showing the empty DataKeyNames property

Figure 6 - The GridView's property grid

What I really wanted was that the GridView was able to figure out what property of my business object represents the object's primary key. Fortunately, there's another attribute that you can use: the DataObjectFieldAttribute. This attribute has a number of overloads that allows you to specify whether the property is a primary key and an identity. It also allows you to indicate it's length in bytes and whether or not it's nullable. Applying the attribute to the Bug's Id property results in this code:

VB.NET
<DataObjectField(True, True, False)> _
Public Property Id() As Integer
  Get
    Return _id
  End Get
  Set(ByVal value As Integer)
    _id = value
  End Set
End Property

C#
[DataObjectField(true, true, false)]
public int Id
{
  get { return id; }
  set { id = value; }
}

This attribute indicates that the Id property is a primary key and an identity in the database (the first two parameters of the attribute's constructor) and that it's not nullable (the third parameter).

The next time you run the Configure Data Source Wizard, the GridView is able to figure out what the primary key for the business object is, and fill in the DataKeyNames property for you automatically:

Properties Grid for the Grid View with the DataKeyNames attribute filled in

Figure 7 - The GridView's property grid with the DataKeyNames property filled in correctly

At first, this may not seem like a big time saver. However, if you configure many ObjectDataSource controls a day, you'll quickly start appreciating this behavior. But, better than saving time, this attribute improves the quality of your code: since the attribute is set automatically, you can no longer forget to set it manually, so your updates and deletes will function as expected, right out of the box.

Summary

Attributes are a great way to improve the quality of your code. By applying these attributes, you decorate your classes with useful metadata, allowing both an IDE like Visual Web Developer or Visual Studio and run-time code to inspect your elements to gain a better understanding of their capabilities. The idea is that you specify an attribute once when you write your code, but you benefit from it many times.

There are many more attributes in namespaces like System.ComponentModel and System.Diagnostics, making your life as a developer a whole lot easier.
Are you using special attributes to decorate the elements you code that increase your productivity? Leave a comment at the end of this article, so others can benefit from your knowledge as well.

In a future installment of this series, I'll take a look at a number of attributes that make debugging your code a lot easier.


Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Tuesday, July 04, 2006 11:30:04 AM vivek kumar dixit said:
Dear sir,
             plz send vb.net  beginner tutor notes
            
              with regrad's
              
                 vivek
On Tuesday, July 04, 2006 8:42:07 PM Imar Spaanjaars said:
Hi Vivek,

How is this related to the original topic "Using Attributes to Improve the Quality and Usability of Your Code "??

Imar
On Monday, July 10, 2006 8:49:07 PM michiel said:
Hey, great article. Nice to see some Visual Studio 'secrets', it's interesting how none of it is automagic, it's all based on language features.
Anyway, here's my question: how does this make it possible to do sorting or paging using custom domain objects?
On Friday, July 14, 2006 6:10:45 PM Imar Spaanjaars said:
Hi Michiel,

You can do that by creating a class that implements the generic IComparer(Of YourClass) interface. You can use this class to sort a Generics List by calling the Sort method on the class.

A method in your businss layer can receive a sortExpression (passed from a GridView to an ObjectDataSource and then passed to your BLL method) and then use that expression to sort the list.

My latest book, ASP.NET 2.0 Instant Results (http://www.spaanjaars.com/AboutMyBooks.aspx?aboutitem=instantresults) talks about this in Chapter 12 - The Wrox BugBase.

Cheers,

Imar
On Wednesday, September 13, 2006 6:18:36 PM Andy Green said:
Hi Imar

Many thanks for the article.

I'm just discovering the DataObjectXxxAttributes, and I arrived after Googling DataObjectFieldAttribute. The article has cleared up a number of issues, but I had one remaining question. Does the IsIdentity property apply specifically to Integer Identity columns, or does it apply to any server-assigned key? I'm using UniqueIdentifiers with a default of NEWSEQUENTIALID, and I'm wondering which way to set the IsIdentity property of the attribute. Any information you have would be gratefully received.

Regards

Andy
On Friday, September 15, 2006 2:00:12 PM Imar Spaanjaars said:
Hi Andy,

Sure you can. Simply change the property from an int to a Guid. E.g.:

Public Property Id() As Guid
  Get
    Return _id
  End Get
  Set(ByVal value As Guid)
    _id = value
  End Set
End Property

public Guid Id
{
  get { return id; }
  set { id = value; }
}

You can still apply the same DataObjectField attribute to turn this property into a PrimaryKey field.

Cheers,

Imar
On Sunday, October 15, 2006 2:09:28 PM Steve said:
I wish I found this earlier.  Great information.

My question,

By using DataObjectFieldAttribute we can get the Gridview DataKeyNames property to properly fill.  

Can something similar be done for the gridview column properties?

Example:

[DataObjectAttribute()] _
Public Class Contact_Info

  Private _LastName As String
  Private _ContactID As Integer

  [DataObjectField(True,True,False)] _
  Public Property [ContactID]() As Integer
      Get
          Return _ContactID
      End Get
      Set(ByVal Value As Integer)
          _ContactID = Value
      End Set
  End Property

  [DataObjectField(False,False,False)] _
  Public Property [LastName]() As String
      Get
          Return _LastName
      End Get
      Set(ByVal Value As String)
          _LastName = Value
      End Set
  End Property


End Class

For Example:

[asp:GridView ID="GridView1" runat="server"
        AutoGenerateColumns="False"
        DataKeyNames="ContactID"
        DataSourceID="ObjectDataSource1"]

  [Columns]
    [asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" /]
  [/Columns]
[/asp:GridView]

By defualt the data field name and the header text are the same.  

Is there a way to use a different header text by default.  

For example:

[asp:GridView ID="GridView1" runat="server"
        AutoGenerateColumns="False"
        DataKeyNames="ContactID"
        DataSourceID="ObjectDataSource1"]

  [Columns]
    [asp:BoundField DataField="LastName" HeaderText="SurName" SortExpression="LastName" /]
  [/Columns]
[/asp:GridView]


[asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
     TypeName="Contact_Controller"
     DataObjectTypeName="Contact_Info"
     SelectMethod="SelectAllMethod" ]

[/asp:ObjectDataSource]
On Sunday, October 15, 2006 2:25:20 PM Imar Spaanjaars said:
Hi Steve,

Interesting question. I could definitely use something like this myself. But I don't think there are existing attributes that allow you to do this. I searched the System.ComponentModel namespace but found nothing useful. (http://msdn2.microsoft.com/en-us/library/system.componentmodel.aspx)

I think it should be possible to make something like this yourself. You could create a custom attribute that has a DisplayName property (or something  like this). You could then create a new class that inherits from GridView that looks at this attribute on a business object to determine the correct columns.
In addition, or alternatively (i haven't thought this through very well yet) you may need to derive a class from BoundField and look or the attribute there.

Cheers,

Imar
On Friday, January 26, 2007 9:52:52 PM LurkingVariable said:
First off... GREAT article. Thank you!

In the beginning you say you "moved a number of methods from the BugManager class into the Bug class itself". Is this because (A) you feel this is a better approach or (B) just for the purposes of this demo and you don't recommend it, or (C) this is the way to do it when binding to an ObjectDataSource?

Also, I am a fan of Smart, Strongly Typed collections (or lists). I usually have a Parent object that has a Children property that returns a "smart collection", which provides the Add/Update/Remove functionality. Have the "UpdateChild" property on the Parent object doesn't feel right to me, 'cause the collection ought to be handling this. This doesn't seem to play well naturally with the ObjectDataSource which expects the GetList() and Add/Update/Remove function to be on the same object (whereas I would have the GetList or "Children" property on the parent, then the collection management features on the the smart collection). Essentially, I would never have a child collection floating around on it's own without a parent so the collection object will always be an "Instance", not a static/shared object. So... I plan set the ObjectDataSource TypeName to "MySmartCollection" and then in the ObjectCreating event set the ObjectInstance to my collection. Only problem is... I haven't seen anyone else doing this. Do you see any flaws in my approach?

Thanks again,

-LV
On Saturday, January 27, 2007 8:42:17 PM Imar Spaanjaars said:
Hi LurkingVariable,

Glad you like the article.

I moved those methods to the same class so the entire Bug class is now responsible for managing its own data and database interaction. In the BugBase application in my book I separated data (the Bug class) and behavior (the BugManager class), but you could easily combine the two. I use the latter method (combining) more often these days as they are easier to generate with code generators. Other than that, it's mostly a matter of preference. From an OO point of view, I think it's better to have them in one class; since the Bug class is the one that knows the most about itself, it should also deal with insert, update, select and delete methods.

I see your point about child collections. I often design classes like that as well, where the parent class is responsible for saving its child classes. However, this doesn't always work fine with data binding.

Quite often, I bind, say, a GridView to a child collection directly and update from there. I haven't tried your solution yet, but if it works, it works... ;-)

For a very detailed approach and framework for this (including a custom ObjectDataSource) you may want to to take a look at Rockford Lhotka's CSLA framework: http://www.lhotka.net/

Cheers,

Imar
On Thursday, February 22, 2007 11:53:55 PM Carlos Rivero said:
thanks for the article ! is great!!

It would be great that if we set:
[DataObjectField(true, true, false)]
it would automaticly add ConvertEmptyStringToNull="false" to the BoundField....

I've having to go BoundField by BoundField and add  ConvertEmptyStringToNull="false" other wise when I empty a text box and do update I get: Object reference not set to an instance of an object.


On Tuesday, April 10, 2007 8:39:24 AM Bharat said:
Nice article...So simple to understand the concept behind every move.
Imar: can you list some of the eBooks you refer for Building Standard Business objects
On Tuesday, April 10, 2007 9:17:48 AM Imar Spaanjaars said:
Hi Bharat,

Sorry, no eBooks, but you may want to take a look here:

http://www.spaanjaars.com/QuickDocId.aspx?quickdoc=416

Why did you post your comment 32 times? Did you get an error? I just spent some time removing them all again... ;-(

Imar
On Saturday, April 14, 2007 4:01:08 PM Bharat said:
Imar - Thanks for your prompt reply

Sorry it was an error - the site / my bandwidth was slow hence.

Bharat
On Tuesday, September 18, 2007 10:28:36 AM Praful said:
Thank U..
On Monday, April 14, 2008 12:23:08 PM Rakesh Chauhan said:
The article is really a refreshing look to the secrets of dot.net
On Monday, October 20, 2008 8:57:25 PM Joseph Marinaccio said:
Thank you for another great article.
On Friday, December 19, 2008 6:45:47 PM Saif said:
I haven't visited your site in months, my bad! Excellent work. Can you share the list of attributes you use. I've been trying to hunt down a list for a while now.
On Friday, December 19, 2008 8:10:56 PM Imar Spaanjaars said:
Hi Saif,

I don't have a list. However, you can check out this article, and part 2 of it. Additionally, checking out my N-Layer design article will give you a real world implementation of them.

Finally, take a look at the System.ComponentModel namespace.

Cheers,

Imar
On Friday, March 13, 2009 6:09:09 PM Josh said:
In the section of this article entitled "An Introduction to the Bug Class", the first sentence reads:

"The understand what the Bug class is..."

I believe this should be "*To* understand..."
On Friday, March 13, 2009 6:18:09 PM Josh said:
Also, I'm not sure I'm convinced of the value of attributes in this situation.  After reading about attributes elsewhere I immediately started adding them to my business objects.  The downside is that it clutters my code and hurts readability a little and I'm not really sure the payoff is worth that.  


It seems like the value is that in using VS wizards you get some stuff automatically selected.  For me I rarely use the wizards for anything so I question whether there is a use for them.
On Friday, March 13, 2009 7:54:11 PM Imar Spaanjaars said:
Hi Josh,

Fixed, thank you.

Using attributes for this is a matter of preference. You can easily collapse them to a single line so they don't get much in the way.

If you do use the ObjectDataSource you'd be nuts not to use the wizards. They don't generate bloated or hard to understand code; they generate exactly the same code you'd need to write by hand otherwise. I think someone using the wizard can beat a hand coder hands down by a factor 10.... ;-)

But of course, it's all a matter of preference. I prefer to let tools do the heavy typing (not coding!!) for me. That's why, for example, I am a big fan of Coderush and Refactor!

Cheers,

Imar
On Wednesday, October 07, 2009 6:43:49 AM Jignesh said:
Hi,
Great articles. This article is really benefited to new guys who wants know about attributs. Till today i didnt know this kind of possibility but know i can do this kind of programming. Thanks imar.

Some questions,

1) what we have to do if we have more than one primary key in table ?
    should we have to follow same pattern which u mentioned for single primary key ?

2) this kind of attribut you set for data keys so what it should return?
    when i try to delete some row from gridview , will this return single primary key or it will return object?

Thanks
On Wednesday, October 07, 2009 9:40:45 PM Imar Spaanjaars said:
Hi Jignesh,

1) Don't know. haven't tried it yet. Have you tried applying the attribute to multiple properties that are part of your composite key?

2) The attribute doesn't return the actual underlying ID value. It just states that a specific column contains the primary key. This is a Design Time issue and helps Visual Studio figure out the keys so it can, for example, set up a DataKeyNames property correctly on a GridView. At run-time, standard .NET behavior applies.

Cheers,

Imar
On Thursday, January 28, 2010 5:33:06 PM yashwanth said:
Hi Imar,
Nice article and when I try to do this in a loosely coupled way by using the interfaces and properties divided into different applications and bind them together, this methodology is not working for me. I can only see the properties on the choose a business object wizard screen and when I select one from it and go to next page, it is not showing any method for select, Insert, Update and Delete options. Any idea why it is not working for me?
On Friday, January 29, 2010 1:36:25 PM Imar Spaanjaars said:
Hi yashwanth,

Did you apply the attributes to the Interface or to the class? I don't think they bubble down from the interface definition.

Cheers,

Imar
On Friday, January 29, 2010 1:43:48 PM yashwanth said:
Hi Imar,
Thanks for the quick response, I actually figured it out. I was really dumb to apply the attributes to properties, and by the way we cannot apply this attribute to Interfaces.
On Friday, March 16, 2012 11:51:31 AM zack@12 said:
I have an interview next week. Can you please post some sample interview questions on .NET? I found a few sites below that have some questions, but need a few more samples to feel confident!

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.