Validating Multiple Data Model Properties
In my
previous article I wrote about displaying model state errors inside
databound controls such as GridView. In that example we used data annotation
validators to perform the validations. While data annotation validators do their
job quite well they are inherently applied to only one data model property at a
time. For example, the [StringLength] or [Required] attributes validate only one
property under consideration. However, sometimes your validation rule involves
multiple data model properties. In such cases data annotation validators won't
be of much use. Luckily there are other validation techniques that can come to
your rescue. This article is doing to discuss two of them:
- Validating using ModelState dictionary
- Validating using IValidatableObject interface
In the first technique you use ModelState dictionary to access the model
properties, check their values and then flag errors if necessary. Let's
understand how this technique is used with an example. The example mentioned
here uses the same Employee data model class that was used in my previous
article. So, if you haven't created the Entity Framework data model yet take a
moment to generate it from Northwind database by referring
this article.
Now, consider the following code that shows the GridView_UpdateItem method:
public void GridView1_UpdateItem(int employeeid)
{
NorthwindEntities db = new NorthwindEntities();
Employee item = db.Employees.Find(employeeid);
if (item == null)
{
ModelState.AddModelError("", String.Format("Item with id {0} was not found", employeeid));
}
else
{
TryUpdateModel(item);
if (item.FirstName.Length > 10 && item.LastName.Length > 10)
{
ModelState.AddModelError("", "First Name and Last Name are invalid!");
}
if (ModelState.IsValid)
{
db.SaveChanges();
}
}
}
Notice the code shown in bold letters. The code checks the length of
FirstName and LastName model properties. Accordingly an error message is added
in the ModelState dictionary using AddModelError() method. Supplying the first
parameter as an empty string indicates that this is a model level error rather
than a property level error. If you want to associate the error under
consideration to a specific model property you can specify the name of the
property as the first parameter of the AddModelError() method. The second
parameter of the AddModelError() method specifies the error message that will be
displayed in the ValidationSummary control.
Ok. Now let's see the other technique - IValidatableObject interface. In this
technique you need to implement IValidatableObject interface on the data model
class. The IValidatableObject interface contains Validate() method that should
return a collection of ValidationResult objects. Each ValidationResult
represents an error condition with the underlying data model. Since Employee
model class is an EF designer generated class the best place to implement the
IValidatableObject interface is the Employee partial class you created for the
sake of associating metadata class with the model class. The following code
shows how the Validate() method can be implemented on the data model.
[MetadataType(typeof(EmployeeMetadata))]
public partial class Employee:IValidatableObject
{
public IEnumerable Validate(ValidationContext validationContext)
{
List errors = new List();
if (FirstName.Length>10 && LastName.Length>10)
{
ValidationResult err = new ValidationResult("Invalid First Name and Last Name");
errors.Add(err);
}
return errors;
}
}
Notice the code shown in bold letters. The Validate() method receives
ValidationContext as a parameter and returns IEnumerable of ValidationResult
objects. Inside, it creates a generic List of ValidationResult objects. It then
checks for the same validation rule as before and if the validation rule is
being violated adds a new ValidationResult to the generic list. The
ValidationResult constructor accepts an error message that flags the error.
Finally the generic List is returned from the Validate() method.
Which of the two techniques you use depends on your requirement. The
ModelState technique puts the validation code in the web form since ModelState
is a property of the Page base class. That means your validation logic gets
coupled with the rest of the web form code. This may be alright if the
validation rule is to be checked for that form alone. However, if the validation
rule is applicable inside all the web forms (or wherever Employee data model
class is being used) then IValidatableObject technique would be more useful.
Using IValidatableObject technique allows you to put the validation rule under
consideration in the model class itself. So, depending on your requirement you
should pick the appropriate technique.
While the above example illustrates both of these techniques for a web forms
application they can be used with ASP.NET MVC application also in the same
fashion. If you are using ASP.NET MVC the ModelState dictionary technique will
go inside an action method whereas the IValidatableObject technique remains
unchanged.
That's it for now! Keep coding.