Well some updates to my old article on ASP.NET MVC Validation today.
The bad news, I had to drop “Solution B” which was to allow you to define validation rules on a metadata type “buddy” class of your model, in cases where you may be using something like LINQ2SQL and are unable to make changes to your model classes being auto-generated for example.
The good news is, there is an alternative.
Most of us would have heard of Steve’s brilliant new swanky ASP.NET MVC Validation Framework coined xVal which allows you to use any validation component you wish (Castle component, NHibernate, DataAnnotations..) and any client-side validation library (such as jQuery Validation) and allow model-based specification of your validation rules.
It really is beautiful, and blew my article on validation out of the water. So what I would like to discuss is; how to gear up xVal to use these Metadata Type “buddy” classes that I spoke of above and allow you to specify such validation attributes on these buddy classes for instances where you do have such a frozen data model; particularly in terms of LINQ2SQL or Entity Framework where the data model is code-generated but you still wish to apply model-level rule specification. So this walkthrough will explain how to perform this in conjunction with the xVal framework. You will note on the latest xVal 0.8 release from Steve that he allows “ad-hoc” rule definition on the fly if you roll that way, but as I stated in my last article and Steve reiterates that the reasons for placing your validation rules on your model are clear and it also adheres to the DRY principal, which is a good thing (a term heavily popularised by Rails).
So let’s get started.
Download the latest version of xVal and familiarise yourself by reading some of the tutorials if you haven’t already, and then fire up his “Bookings Demo” sample app as I make reference to it in this example. However if you wish to jump straight to an implementation using LINQ2SQL check out the bottom of this article, otherwise we will continue with a simple example first and get you familiarised with the MetadataType attribute. You may need to reference the latest version of xVal you download and the .js scripts if you are using his demo.
Once you have all that up and running, open your data model class. It should look something like this:
public class Booking
{
[Required]
[StringLength(15)]
public string ClientName { get; set; }
[Range(1, 20)]
public int NumberOfGuests { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime ArrivalDate { get; set; }
}
You can see the validation attribute rules placed on the model, now if this had been a Booking designer-generated LINQ2SQL class, we would have not been able to safely place the validation attributes on these properties as they would then be deleted next time your LINQ2SQL classes are re-generated. So this is where the Metadata Type attribute comes in, let’s update it to look something like this:
///
/// Booking data model class.
///
[MetadataType(typeof(BookingMetadata))]
public class Booking
{
public string ClientName { get; set; }
public int NumberOfGuests { get; set; }
public DateTime ArrivalDate { get; set; }
}
///
/// Booking data model Metadata Type class.
/// All validation rules should be placed here and will be evaluated
/// If property names match a reference in the 'Booking' class.
///
public class BookingMetadata
{
[Required]
[StringLength(15)]
public string ClientName { get; set; }
[Range(1, 20)]
public int NumberOfGuests { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime ArrivalDate { get; set; }
}
You will notice I moved all validation attributes to the “Metadata” class, which we refer to as a “buddy” class and I declared the Booking class with an attribute: [MetadataType(typeof(BookingMetadata))]. This tells the Booking class to go look for additional metadata in a separate class, enabling us not to deal with the Booking class directly. Wamoo. We’re getting there. Now you need one final change to the included DataAnnotationsValidationRunner.cs file incorporated in Steve’s project. Replace GetErrors(object instance) with this one (Courtesy of Steve!).
///
/// Get any errors associated with the model also investigating any rules dictated by attached Metadata buddy classes.
///
///
///
public static IEnumerable GetErrors(object instance)
{
var metadataAttrib = instance.GetType().GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType().FirstOrDefault();
var buddyClassOrModelClass = metadataAttrib != null ? metadataAttrib.MetadataClassType : instance.GetType();
var buddyClassProperties = TypeDescriptor.GetProperties(buddyClassOrModelClass).Cast();
var modelClassProperties = TypeDescriptor.GetProperties(instance.GetType()).Cast();
return from buddyProp in buddyClassProperties
join modelProp in modelClassProperties on buddyProp.Name equals modelProp.Name
from attribute in buddyProp.Attributes.OfType()
where !attribute.IsValid(modelProp.GetValue(instance))
select new ErrorInfo(buddyProp.Name, attribute.FormatErrorMessage(string.Empty), instance);
}
There you are. That’s all there is to it, now if you re-build, try submitting the form you will see jQuery’s clientside form validation kick in, and if you submit correctly and perhaps breakpoint and change a value such as the NumberOfGuests to 50 you will see that this then breaks the validation range rule of 1-20, so server-side validation will perform and your MetadataType class will be queried and used!
LINQ2SQL IMPLEMENTATION:
Now if you wish to use this in a real world example on a frozen class, perhaps for LINQ2SQL where you cannot change the class directly for rule additions, create a new class file named Booking.Metadata.cs for example, if Booking is the class you wish to add validation rules to and add it to your data model project in the same folder where your .dbml resides. Now with your default class signature specified, alter it to partial, add your MetadataType attribute and specify your validation rules in your new Metadata class. The final output should look something like this:
///
/// Partial Booking data model class.
///
[MetadataType(typeof(BookingMetadata))]
public partial class Booking
{
}
///
/// Booking data model Metadata Type class.
/// All validation rules should be placed here and will be evaluated
/// If property names match a reference in the 'Booking' class.
///
public class BookingMetadata
{
[Required]
[StringLength(15)]
public string ClientName { get; set; }
[Range(1, 20)]
public int NumberOfGuests { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime ArrivalDate { get; set; }
}
Download Sample Application: xVal Bookings Demo with MetadataType Attributes
Happy validating!
19 Comments
Wow… this is exactly what I needed! Thanks so much for taking the time to write out this blog post!
http://zi.ma/webdev
http://twitter.com/elijahmanor
Thanks.. This is great.. im been looking for this for 2 days.. thanks
No problem. Glad I could help.
I use the Entity Framework and I got the code to work! Wohoo. But now I have a problem on the controller side.
view:
was
Html.TextBox(“country_id”)
now:
Html.TextBox(“country.country_id”)
controller:
public ActionResult Create(Country coutry_to_create)
coutry_to_create has only empty values.
If I change the view to what it was it works again…
How do i get the value out of country.country_id?
@Tom: ‘country_to_create’ would have to be named ‘country’. Read here http://weblogs.asp.net/scottgu/archive/2008/10/16/asp-net-mvc-beta-released.aspx#three
Hi!
Great site.
I was wondering something that you may know the answer to.
Firstly, by creating the meta type classes would this remove the need for a RuleException class as described in the free chapter in the MVC book by Scott Gu?
Secondly, if you use something like LinqToSQL/Entity Framework why can’t we use the attributes that it picks up off the database to determine the length required, not null etc validation rules?
Thanks
Hi Jon,
You’d most likely have to pose these questions at Steve. The rules in your second point are data annotations found in System.ComponentModel.DataAnnotations and as far as I know the xVal framework supports this. Check this out for a starting point http://blog.codeville.net/2009/02/27/xval-08-beta-now-released/.
Thanks.
Unless I’m missing something, this seems a lot of extra work as you will have to go through all tables noting their columns, lengths and then create classes for all these with the correct attributes to support validation.
Is there a tool that looks at the Model Designer and then takes the tables/columns details and creates the classes.
Why do developers have to manually write the classes? If LinqToSQL/EF can pick up the constraints of columns why isn’t the validation built from this? I understand if your model is not Linq etc but surely this is a must feature of Linq/EF that someone has missed?
I have found this, which might be a quicker way of doing things?
http://blog.maartenballiauw.be/post/2009/01/30/Form-validation-with-ASPNET-MVC-release-candidate.aspx
Yes but if you see here, I believe you still need to implement custom error logic (http://www.asp.net/learn/MVC/tutorial-37-vb.aspx). A framework could determine the rules from the database schema, not sure why we haven’t heard about any that do. Very odd. But I don’t mind the fine grain control, perhaps a mixture of both being allowed would be good?
RoR’s framework does this out of the box in their model.
Thank you, I already know about xVal but this blogpost helped me when I was actually ready to start using it.
Keep up the great work.
Thanks! This is exactly what I was looking for, really saved me some time.
Is there a way to get a Validation Runner which uses reflection to check types from the database?
So that say I have a varchar(50), if the string is over that length I can get an error to say “Your string is too long”.
I haven’t tried to implement metadata yet, but have been able to use LINQ2SQL as a repository interface implementation with each class also implementing interfaces used by the repository. Just create a file with a different name than the LINQ2SQL data context file and use partial classes. They don’t go away with rebuilds of the data context. Just have to outsmart the compiler. I return IMyInterface and IEnumerable from the repository methods. The Cast method on datasets returned by LINQ select allows mapping out to the interface type.
Hi
i just have one small question as i don´t find the solution…
when using Datatype.Date, it waits a date format yyyy-mm-dd… is there a possible way to ask him to look another one or do i have to create a custom rule ?
Thanks for the article
This post is very useful.
Thank you very much!
thanks for great post.
could you include a sample for editing? I have a problem with the model binder when editing an existing entity where the form fields are prefixed.
looks good but does not work for me, no errors are ever returned , validation always passed even when rules are not being met
I suspect the error is in the DataAnnotationsValidationRunner class, any help would be appreciated, I have followed exactly the steps in the demo
4 Trackbacks/Pingbacks
[...] support for the metadata type attributes. Both of which I do not have time to do. I have outlined a new approach over in this article with the heads up from Steve Sanderson on how to incorporate his xVal framework with the metadata [...]
Use MetadataType Attribute with ASP.NET MVC xVal Validation Framework…
Thank you for submitting this cool story – Trackback from DotNetShoutout…
[...] Here is the original: Using MetadataType attribute with ASP.NET MVC xVal Validation … [...]
[...] a comment » jquery validation xVal Framework DataAnnotations IDataErrorInfo Buddy Classes Method Enterprise Library Validation Application Block – Integrating Enterprise Library Validation [...]
Post a Comment