Business Entity Metadata With Attributes

Today I created a little validation library that utilizes Attributes to specify simple business rules on an entity. It didn’t take that long to code, and seems to work pretty well:

...
[RequiredValidator( "First name is required." )]
[RangeValidator( 0, 50,
   "First name cannot be longer than 50 characters."  )]
public string FirstName
{
	get{ return _firstName; }
	set{ _firstName = value; }
}

[RequiredValidator( "Birth date is required" )]
[RangeValidator( "1900/1/1", "2010/1/1",
   "Birth date must be after 1900/1/1" )]
public string BirthDate
{
	get{ return _birthDate; }
	set{ _birthDate = value; }
}
...

The attribute based approach seems like it would work quite well for specifying simple business rules. The main advantages as I see them are:

  • Simple, easy to read declarative approach. It’s a lot easier to see at a glance what the rules are for each property.
  • It should be possible to use these rules to generate UI validation controls.
  • Keeps business rules where they belong; with the entity they pertain to.

I still haven’t given much thought to the disadvantages of this approach, however there are at least a couple:

  • Attributes somewhat limit what you can accomplish. For example, when validating a birth date, you’d ideally set sane lower bound (1900/1/1 as I do in the above example). However, since you must specify constant values in an attribute, you can’t set the upper bound to DateTime.Now, which would be ideal. Instead, you have to hard-code a specific value.
  • They clutter up your business classes. With one or two attributes on each property, this isn’t a big problem. But what happens when you start using attributes to map to UI components, or database tables? You could get something ugly like this:
[DatabaseColumn( "FirstName" )]
[UserInterface( typeof(TextBox), "txtFirstName" )]
[RequiredValidator( "First name is required." )]
[RangeValidator( 0, 50,
   "First name cannot be longer than 50 characters."  )]
public string FirstName
{
	get{ return _firstName; }
	set{ _firstName = value; }
}

The other issue to wrestle with is whether to assign these attributes to properties, as I do above, or to the underlying fields which they are hiding. Ideally your underlying fields will be marked private, so if you do map to fields you could run into issues with Trust, depending on the environment to which you are deploying. Either way, it’s definitely something to think about.

I’ll post the code for the validator library as soon as I find the time to fire up my laptop.