Data validation is important topic in applications. There are many validation frameworks available and there should be one that you are happy with. I am currently playing with Enterprise Library 4.1 Validation Application Block and I am integrating it to my ASP.NET MVC application. In this posting I will show you how to use validation block in your ASP.NET MVC application.
Note. This posting gives you first ideas about validation and shows you how to get things done quick and dirty. For production-ready validation there more steps to follow and I will introduce these steps in my future postings. Stay tuned!
Introduction
Shortly, you can create ASP.NET MVC views that craete and initialize objects for you. I assume you know this feature and you know how it works at basic level.
Here is how my application is layered.
Currently all external stuff is referenced by infrastructure layer. Infrastructure layer provides common interfaces for dependency injection and validation. These interfaces doesn’t change when implementations change. Presentation layer uses infrastructure resolver to get implementations of repositories.
Adding validation
I have Enteprise Library 4.1 downloaded and installed on my development machine. If you want to just test my solution you can also create one ASP.NET MVC web application project and put all stuff there. No problem at all. After installing Enterprise Library you need some references so your application can use validation block. Take these files:
- Microsoft.Practices.EnterpriseLibrary.Common.dll
- Microsoft.Practices.EnterpriseLibrary.Validation.dll
- Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.dll
These libraries should be enough. I added references to these libraries to my infrastructure library.
As a next thing we need facade for our validation feature. I created these three classes:
- ValidationError – represents one validation error and contains properties for invalid property name and validation message.
- ValidationException – exception type that contains array of validation errors (it is also possible to detect validation caused errors in code).
- Validator – this class has only one method called Validate<T> and it makes all the real work.
Let’s see those classes now.
ValidationError
public class ValidationError
{
public string PropertyName { get; set; }
public string Message { get; set; }
}
ValidationException
public class ValidationException : Exception
{
private readonly ValidationError[] _errors;
public ValidationException(ValidationError[] errors)
{
_errors = errors;
}
public ValidationError[] ValidationErrors
{
get
{
return _errors;
}
}
}
Validator
public static class Validator
{
public static ValidationError[] Validate<T>(T instance)
{
var errors = new List<ValidationError>();
var results = Validation.Validate<T>(instance);
foreach (var result in results)
{
var error = new ValidationError();
error.PropertyName = result.Key;
error.Message = result.Message;
errors.Add(error);
}
return errors.ToArray();
}
}
Now we are almost done and it is time to add some rules.
Adding validation rules
Make sure you have web.config file in your application because we are going to modify it. Run Enterprise Library configuration program from all programs menu and open your web.config file.
Add some validation rules for you classes and save configuration. Enterprise Library Configurator creates all required sections to your web.config file automatically.
Validating objects
As a first thing take a look at this simple form that let’s users insert new price enquiries.
<h2>Insert</h2>
<%= Html.ValidationMessage("_FORM") %>
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>New price enquiry</legend>
<table>
<tr>
<td valign="top"><label for="Title">Title</label>:</td>
<td valign="top">
<%= Html.TextBox("Title") %><br />
<%= Html.ValidationMessage("Title")%>
</td>
</tr>
<tr>
<td valign="top">
<label for="From">From</label>:
</td>
<td valign="top">
<%= Html.TextBox("From") %><br />
<%= Html.ValidationMessage("From")%>
</td>
</tr>
<tr>
<td valign="top"><label for="DocNumber">Number</label>:</td>
<td valign="top">
<%= Html.TextBox("DocNumber") %><br />
<%= Html.ValidationMessage("DocNumber") %>
</td>
</tr>
<tr>
<td valign="top"><label for="Date">Date:</label>:</td>
<td valign="top">
<%= Html.TextBox("Date", DateTime.Now.ToShortDateString()) %><br />
<%= Html.ValidationMessage("Date") %>
</td>
</tr>
<tr>
<td valign="top">
<label for="DueName">Due date:</label>:
</td>
<td valign="top">
<%= Html.TextBox("DueDate", DateTime.Now.ToShortDateString()) %><br />
<%= Html.ValidationMessage("DueDate") %>
</td>
</tr>
</table>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
Let’s see one repository method that accepts object to be validated. Let’s assume we have repository that validates objects before saving them. If there are validation errors ValidationException will be thrown. Here is simplified save method of repository.
public void SavePriceEnquiry(PriceEnquiry instance)
{
var results = Validator.Validate<PriceEnquiry>(instance);
if (results.Length > 0)
throw new ValidationException(results);
Save<PriceEnquiry>(instance);
}
And let’s use this repositoy in ASP.NET MVC controller (if your version of ASP.NET MVC doesn’t support HttpPost sttribute you can use AcceptVerbs(HttpVerbs.Post) instead).
[HttpPost]
public ActionResult Insert(PriceEnquiry enquiry)
{
try
{
_repository.SavePriceEnquiry(enquiry);
}
catch (ValidationException vex)
{
Helper.BindErrorsToModel(vex.ValidationErrors, ModelState);
return Insert();
}
catch (Exception ex)
{
ModelState.AddModelError("_FORM", ex.ToString());
return Insert();
}
return RedirectToAction("Index");
}
You can see call to method called BindErrorsToModel(). This is helper method that steps through validation errors array and binds errors to current model. You can take this method and use it in your own projects if you like.
public static class Helper
{
public static void BindErrorsToModel(ValidationException exception, ModelStateDictionary modelState)
{
BindErrorsToModel(exception.ValidationErrors, modelState);
}
public static void BindErrorsToModel(ValidationError[] errors, ModelStateDictionary modelState)
{
if (errors == null)
return;
if (errors.Length == 0)
return;
foreach (var error in errors)
modelState.AddModelError(error.PropertyName, error.Message);
}
}
NB! Don’t forget that fields in your views must be named like properties of class you are expecting as a result of binding.
Now you can test your application and see if validation works. You should see correct error messages if everything went well and there are no bugs in code or configuration.
Conclusion
Although this example is long one it is not hard to add validation support to your applications. It takes you some time if it is your first time but if you are familiar with tools and you keep yourself from planning rocket sience level validation then everything goes fast and smooth.
There are some more nyances you should know before stating that you fully suppor validation through your application. I will introduce some more ideas in my future postings about validation.
View Comments (8)
I just use existing validation functionality in ASP.NET MVC 2.0 and .NET 4.0.
Just decorate model classes with data annotation attributes and that's it. Looks somthing like this:
public class SignupForm
{
[Required]
public string UserName { get; set; }
}
then in action method:
[HttpPost]
public ActionMethod Signup(SignupForm form)
{
if (ModelState.IsValid())
{
Membership.CreateUser(form);
}
return View();
}
This is a case of bad usage of exceptions. If the user forgot to enter the number is an exception of the normal flow of your application?
What if the database turns offline? Does the application will send an email to Benedict XVI?
Sorry, but this is not a use case for exceptions. This is really bad, you need to read.
The use of exceptions aren't that bad. If we try to persist an invalid entity into the repository, we are violating the contract and an exception is what we deserve.
However, the controller should initiate the validation before we use the repository. Then the controller can decide to continue to persist the entity, or present the validation errors to the user.
I guess this is one of the details the author left out in favour of a simpler blog post.
Thanks for feedback, Jose!
I'm trying to keep those code samples as minimal as possible to keep focus on the topic of posting. I really don't want to publish here code samples that contain a lot of details that are not important in context of blog posting.
Thanks for feedback, Thomas! :)
I already plan next posting that introduces custom model binders. I will show in this posting how to take validation out from controller and how to make controllers shorter this way. I like this idea more and more but I need to play with custom binders a little bit more to write posting that is useful for readers.
Why I let repository to throw exception? My point is simple - in repository you should anyway avoid situation where some other part of code wants to save invalid entity. Yes, we can validate separately in controllers (or custom binders) but what happens with code that has no controllers and binders (let's say we have command line application)? In this case we also need to be sure that invalid entities are not saved.
No, you are wrong. Exceptions are _exceptions_ of the normal flow. You are using exceptions as part of the normal flow and THIS is a *really* a BAD BAD BAD DESIGN.
You don't let exceptions to hapen and then catch. You need to handle the normal flow.
I don't use exceptions and my validation stuff are simpler than yours. So, this has nothing to do with simplistic approach.
I agree that coder of controllers shouldn't shoot whatever he likes to repositories and then hope that repository detects and resolves all the problems.
In my next posting I will show how to move all the validation stuff away from controller and I will also point out why it is not good idea to put all hope on repositories.
As a second paragraphs here sais - the code here is not role model (like Beavis and Butthead). It is quick'n'dirty and its only purpose is to get validation work without too much side topics and discussions.
Thanks a lot for this wounder full read. It is really very informative.