- Posted by liammclennan on February 10, 2009
For a long time my developer's achilles heel has been the question of how best to perform validation, specifically in the Asp.Net MVC environment. I have finally arrived at something I am happy with, so now I present to you my solution and how I got there.
Influences
My first experience working with the MVC pattern was Ruby on Rails. Here is a controller method from one of my rails projects:
def invoiced
if @entry.save
flash[:notice] = 'Entry was successfully created.'
else
flash[:notice] = 'Entry creation failed.'
end
redirect_to :action => 'list'
end
Rails uses Active Record, an implementation of the Active Record pattern. Each model object has a save method that performs validation and returns a boolean indicating if the model is valid.
When Asp.Net MVC preview 5 was released Scott Guthrie provided a sample implementation of model validation. This has become the basis of my model validation strategy.
Goals
A validation strategy must:
- support complex domain rule validations
- support simple datatype and length validations
- be able to report validation errors to the UI, showing which property caused the error.
Implementation
Each model class is responsible for validation itself and storing any errors. Each error that is recorded has an error message, the name of the property most closely associated to the error, and the erroneous value of that property. Here is the interface that my model classes must implement to be validateable:
public interface IValidateable
{
bool IsValid { get; }
IList Errors { get; }
void AddValidationFailure(string propertyName, object propertyValue, string errorMessage);
void AddValidationFailure(IList violations);
}
A caller, usually a controller action, calls IsValid to check if the entity is valid. If not then it uses the Errors collection to access the individual validation failures and report them to the user.
I added a method to my controller base class to update the ModelState with any validation errors that have occurred:
protected void SetModelErrors(IValidateable entity)
{
foreach (ValidationFailure error in entity.Errors)
{
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
}
}
Because the errors are added to the model state they will be displayed in the view because I have:
<%= Html.ValidationSummary() %>
in my master page and
<%= Html.ValidationMessage("Name", "*")%>
beside each validatable property (replace "Name" with the name of the property). Finally, I can perform validation in my controller actions using the following pattern:
public ActionResult AddToCart(Entity entityToAdd)
{
if (!entityToAdd.IsValid)
{
SetModelErrors(entityToAdd);
return View("Add", entityToAdd);
}
saver.AddToContext(entityToAdd);
saver.SaveAll();
return RedirectToAction("Menu");
}
What's Missing
It is popular to have validation performed on the client side as well. I don't do this because:
- duplication. It means validating twice.
- purity. I think my solution is conceptually pure. The javascript ones are messy.
- complex validation. Client side validation is not conducive to complex business rule validation
- there is no need. If I really want validation without a postback I use AJAX to fire the normal server-side validation.
The End
That is the high level view of my current validation strategy. I expect to tweak it slightly as I change my mind and new versions of the Asp.Net MVC framework are released. I did not discuss at all how the actual validation is performed; that's a topic for a future post.