I have talked about various ways to use the Validation Application Block with the MVC Framework::
but in the new ASP.NET MVC Release Candidate we now have support for IDataErrorInfo.
I am mainly interested in how we can get this new validation mechanism working with a validation framework, such as the Validation Application Block, Castle.Validator, or NHibernate Validator as most developers use a validation framework if at all possible.
Validation Application Block
Taking the Validation Application Block for a spin via IDataErrorInfo can be done as follows. Add Validator Attributes to our Customer Class. Note the attributes are optional. You can also create a more POCO experience by adding the validators in a configuration file:
public partial class Customer
{
public int Id { get; set; }
[StringLengthValidator(1, 50,
MessageTemplate = "Name must be between {3} and {5} chars.")]
public string Name { get; set; }
[StringLengthValidator(1, 75,
MessageTemplate = "Email must be between {3} and {5} chars.")]
[RegexValidator(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
MessageTemplate = "Valid Email Required.")]
public string Email { get; set; }
}
Implement IDataErroInfo on the Customer Class.
public partial class Customer : IDataErrorInfo
{
public string Error
{
get
{
return string.Empty;
}
}
public string this[string columnName]
{
get
{
return DoValidation(columnName);
}
}
protected virtual string DoValidation(string columnName)
{
var results = Validation.Validate<Customer>(this);
foreach(var result in results)
if (result.Key.Equals(columnName))
return result.Message;
return string.Empty;
}
}
The code is pretty straightforward above. I am using the Validation facade class in the Validation Application Block. It would be more prudent to loosely couple it behind something like IValidator or IValidator<T> in the real-world.
One of the issues here is that Validation.Validate will be called for each property that is set, which can be incredibly expensive in terms of performance. The Validation Application Block does some internal caching which I believe will make this a somewhat inexpensive call each time in trivial scenarios, but obviously if you are making some expensive SelfValidation calls this could lead to disaster in terms of performance. When using any validation framework one needs to be careful here, because most use reflection and most will not allow you to only check validation on a per property basis. It is usually all or nothing when it comes to validation and thus validation is being fired over and over again for the entire entity.
When you implement this in such a scenario in your CustomersController:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection form)
{
var customer = new Customer();
try
{
UpdateModel<ICreateCustomerForm>(customer);
// Do Something
return RedirectToAction("Index");
}
catch (InvalidOperationException ex)
{
return View(customer);
}
// ...
}
an InvalidOperationException will be thrown if you return anything but string.Empty from one of the IDataErrorInfo members.
If you are using the DefaultModelBinder, which is the case here, it automatically adds errors via ModelState.AddModelError in the background for you which is why when you re-display the form you will see the errors.
Conclusion
In general, I really like the option of using IDataErrorInfo as part of the validation strategy, but I still haven't decided the best way to use it if at all. Initially it appears I have a lot less control over the validation which may mainly be of value in very trivial situations. Still contemplating it for the meantime. If anyone has any thoughts, it is always appreciated.
David Hayden
Posted
Sat, Jan 31 2009 2:23 PM
by
David Hayden