With the new release of ASP.NET MVC 2 CTP 1 there are a lot of cool new features that are driven by html attributes on your models. Read both The Gu’s and Phil Haack’s blog posts about the release to get the full low down.
I’m guessing that (like myself) a lot of people out there are using ORM’s to generate their models. Yeah? Thought so. Anyway, looking at that previous sentence, the key word is *generate*. You see, if you put property attributes in these generated files, they’re just going to be wiped each time you make a db schema change – far from ideal! ;-)
So where do you put these attributes?
Nope, you can’t put them in a partial class because C# and VB.NET don’t allow you to add attributes to properties defined in another partial class.
Enter the Buddy Class!
The Gu touched on buddy classes in his aforelinked1 post, but didn’t go into them in any detail – so I’m going to try and fill that void with this post.
Say we have a 'Customer' table (similar to The Gu's example) in our database and our orm generates a class like so:
public partial class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public string Country { get; set; }
}
Before we go any further (if you haven’t already) you should add a reference to System.ComponentModel.DataAnnotations.dll to your project – you’re gonna need it.
Next we create a partial class for the generated class and also create it’s buddy class.
The best way to explain this is with code, so here you go:
[MetadataType(typeof(Customer_Metadata))]
public partial class Customer
{
//Custom model stuff here
}
public class Customer_Metadata
{
[DisplayName("First name")]
[Required(ErrorMessage = "First name is required")]
public string FirstName { get; set; }
[DisplayName("Last name")]
[Required(ErrorMessage = "Last name is required")]
public string LastName { get; set; }
[Range(1, 120, ErrorMessage = "Invalid Age")]
public int Age { get; set; }
[Required(ErrorMessage = "Email is required")]
[RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")]
public string Email { get; set; }
[Required(ErrorMessage = "Country is required")]
[UIHint("CountryDropDown")]
public string Country { get; set; }
}
Savvy? Good, because that's really all there is to it! ;-)
1aforelinked - new word? © Charles Vallance 2009
EDIT: Making the buddy class a private inner class
As outlined below by Tyrone and Steve, it's probably best practice to have the buddy class as a private inner class.
E.g.
[MetadataType(typeof(Customer.Metadata))]
public partial class Customer
{
//Custom model stuff here
private sealed class Metadata
{
[DisplayName("First name")]
[Required(ErrorMessage = "First name is required")]
public string FirstName { get; set; }
[DisplayName("Last name")]
[Required(ErrorMessage = "Last name is required")]
public string LastName { get; set; }
[Range(1, 120, ErrorMessage = "Invalid Age")]
public int Age { get; set; }
[Required(ErrorMessage = "Email is required")]
[RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")]
public string Email { get; set; }
[Required(ErrorMessage = "Country is required")]
[UIHint("CountryDropDown")]
public string Country { get; set; }
}
}