Class-Level Validation in MVC 3 with IValidatableObject

Tags: Knockout, pubsub, observer, MVC, jQuery, Ajax, EF, Validation, FluentValidation, Visual Studio 2010, ASP.NET, JSON, FullCalendar, Silverlight, Architecture, Vista, IIS, Generics, NHibernate, WCF, RIA Services, Visual Studio 2008, SQL, STORM!, Nullable, ChannelFactory, netTCPBinding, VSPAT, responsive, design, HTML5, CSS3, MVC WebAPI, MVC 4, WebAPI, JQuery Mobile, ScheduleWidget, recurring events, Ninject, Pluggable, CQRS DDD, Windows

In a previous post I covered simple and custom validators for model properties. Do read that post first to get a sample MVC 3 solution set up. Essentially at this point we have one out-of-the-box and one custom validator on our Widget class:

public class Widget
{
    public int Id { get; set; }
 
    [Required]
    [NameAvailable]
    public string Name { get; set; }
}

Now let's say a widget has two new required properties: NumberOfTeeth and Color. Here we go:

public classWidget
{
    public intId { get; set; }
 
    [Required]
    [NameAvailable]
    public stringName { get; set; }
 
    [Required]
    public int NumberOfTeeth { get; set; }
 
    [Required]
    publicstring Color { get; set; }
}

We need to modify our faked out list in WidgetController to return a list with the new properties:

//
// GET: /Widget/
 
public ViewResult Index()
{
    var widgets = new List<Widget>
    {
        new Widget() 
        {
            Id = 1, 
            Name = "SquareWidget", 
            NumberOfTeeth = 6, 
            Color = "Red"
        },
        new Widget() 
        {
            Id = 2, 
            Name = "RoundWidget", 
            NumberOfTeeth = 8, 
            Color = "Blue"
        }
    };

    return View(widgets);
}

One more thing. Modify the Widget Create.cshtml view and add the two new properties:

@model WidgetApplication.Models.Widget
 
@{
    ViewBag.Title = "Create";
}
 
<h2>Create</h2>
 
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
 
@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Widget</legend>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.NumberOfTeeth)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.NumberOfTeeth)
            @Html.ValidationMessageFor(model => model.NumberOfTeeth)
        </div>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Color)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Color)
            @Html.ValidationMessageFor(model => model.Color)
        </div>
 
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}
 
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Ok we're set up with a Widget class that is a little more complicated and lends itself to some basic cross-property validation. For instance let's say that all red widgets are small in size and have teeth no greater than 6. All blue widgets are medium in size and must have between 8 and 12 teeth. Since these two rules involve more than one property of the class this is a perfect candidate for implementing IValidatableObject on the Widget class. The interface is found in System.ComponentModel.DataAnnotations which is convenient since we've already referenced it in our class. IValidatableObject has one method we need to implement:

IEnumerable<ValidationResult> Validate(System.ComponentModel.DataAnnotations.ValidationContext validationContext)

Let's implement it now in our Widget class:

public class Widget : IValidatableObject
{
    private const int MIN_RED_TEETH = 0;
    private const int MAX_RED_TEETH = 6;
    private const int MIN_BLUE_TEETH = 8;
    private const int MAX_BLUE_TEETH = 12;
 
    public int Id { get; set; }
 
    [Required]
    [NameAvailable]
    public string Name { get; set; }
 
    [Required]
    public int NumberOfTeeth { get; set; }
 
    [Required]
    public string Color { get; set; }
 
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Color.ToUpper().Equals("RED"))
        {
            if (NumberOfTeeth < MIN_RED_TEETH ||
                NumberOfTeeth > MAX_RED_TEETH)
 
                yield return new ValidationResult(
                ValidationMessagesResource.IncorrectRedWidgetToothCount,
                new[] { "NumberOfTeeth" });
        }
 
        if (Color.ToUpper().Equals("BLUE"))
        {
            NumberOfTeeth < MIN_BLUE_TEETH ||
            NumberOfTeeth > MAX_BLUE_TEETH)
            
            yield return new ValidationResult(
                ValidationMessagesResource.IncorrectBlueWidgetToothCount,
                new[] { "NumberOfTeeth" });
        }
    }
}

Well we've put off creating a database long enough. Now we need one because IValidatableObject requires a database context to do its thing. If you installed the the VS 2010 SP1 WebPI Bundle you also installed SQL Server 4.0 Compact Edition. I'm going to use that database because it's quick, easy, and free. Here are the steps:

First hook up the controller to our context. We need our Index method to fetch all widgets in the (soon to be created) database and we need our Create method to write new ones to it:

public ViewResult Index()
{
    return View(db.Widgets.ToList());
}

[HttpPost]
public ActionResult Create(Widget widget)
{
    if (ModelState.IsValid)
    {
        db.Widgets.Add(widget);
        db.SaveChanges();
        return RedirectToAction("Index");  
    }
    return View(widget);
}

Remember the WidgetApplicationContext file we created back with the scaffolder? That's what EF Code First 4.1 will use to create the database. But we have to initialize it so go into your Global.asax and paste this into your Application_Start method:

System.Data.Entity.Database.SetInitializer(new System.Data.Entity.CreateDatabaseIfNotExists<WidgetApplication.Models.WidgetApplicationContext>());

And of course we need to set our connection string in Web.config:

<connectionStrings>
  <add name="WidgetApplicationContext"
        connectionString="data source=|DataDirectory|WidgetDatabase.sdf; Persist Security Info=false;" providerName="System.Data.SqlServerCe.4.0" />
</connectionStrings>

That's it. We've told EF to create a SQL Server 4.0 CE database in our App_Data folder and to use the models in our WidgetApplicationContext to create tables for us automatically. Go ahead and fire up the app and create a new widget called "PurpleWidget" that has 10 teeth and is colored purple (what else?). Notice that EF created our database for us and has inserted the new widget in there. Now let's add some code to our WidgetController Create method to check for class-level validation errors before saving it to the database:

[HttpPost]
public ActionResult Create(Widget widget)
{
    if (ModelState.IsValid)
    {
        var errors = db.GetValidationErrors();
        if (errors.Count() > 0)
        {
            return View(widget);
        }
        db.Widgets.Add(widget);
        db.SaveChanges();
        return RedirectToAction("Index");  
    }
 
    return View(widget);
}

Now's a good time to add a couple of error messages to your string table or just hard-code them in the Validate method. Now build the app and fire it up. Create a new widget called "RedWidget" with 50 teeth and the color Red. Notice how our class-level validation kicks in and your error message displays next to the correct property.

Add a Comment