MVC3 Custom Validation

I’m still trying to find the time to truly learn MVC.  I know I like it better than web forms, it is just a matter of spending the quality time with it at home and at work to get as comfortable with it as I am with web forms.  One area that I could not find the answer to, and kept pushing back for later, is custom validation.  I found many examples of doing custom validation where you hard code some value, but that isn’t what I wanted.  I wanted to do custom validation based on a field being unique in a database and have it work with the DataAnnotations and the whole Html.ValidationMessageFor in Razor.

So, after spending some time focused on it I figured it out.  It really isn’t that hard at all.  In a nutshell you create a new class that implements the ValidationAttribute; this class will do the actual work.  Then you add your new attribute to your model.  Of course I’ll include a simple example.

Assumptions of my example.  I have a table called tblTitle, it contains id, name, and isValid fields.  My project uses Entity Framework.  I create a class to handle my business logic and a class that will act like my model.  I also decorate my model class with appropriate DataAnnotations.  I have a controller, which checks for the model state to be valid before calling the business logic’s update or create method. My business logic class also contains the method to check for a unique name.

We want our name field in the title table to be unique inside that table.  No need to have multiple versions of the same title right?

So, first we create our model.

using System.ComponentModel.DataAnnotations;
using MyProjectMVC3.CustomValidators;

namespace MyProjectMVC3.Models
{

    public class TitleModel
    {
        [Key]
        public int ID { get; set; }

        [Required(AllowEmptyStrings = false, ErrorMessage = "You must provide the name/title for this Title entry.")]
        [StringLength(50)]
        public string Name { get; set; }

        [Display(Name = "Is Active", ShortName = "Active")]
        public bool IsActive { get; set; }
    }
}

Here is my business logic class.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using MyProjectMVC3.Models;

namespace MyProjectMVC3.BusinessLogic
{
    public class TitleBL
    {
        public List<TitleModel> List()
        {
            List<TitleModel> oList = new List<TitleModel>();
            using (MyEntities oEnts = new MyEntities())
            {
                oList = (from t in oEnts.tblTitles
                         select new TitleModel
                         {
                             ID = t.id,
                             Name = t.name,
                             IsActive = t.isActive
                         }).ToList<TitleModel>();
            }
            return oList;
        }

        public TitleModel GetTitle(int id)
        {
            TitleModel aTitle = new TitleModel();
            using (MyEntities oEnts = new MyEntities())
            {
                aTitle = (from t in oEnts.tblTitles
                          where t.id == id
                          select new TitleModel
                          {
                              ID = t.id,
                              Name = t.name,
                              IsActive = t.isActive
                          }).FirstOrDefault();
            }
            return aTitle;
        }

        public bool Create(TitleModel oTitle)
        {
            bool success = false;
            try
            {
                using (MyEntities oEnts = new MyEntities())
                {
                    tblTitle newTitle = new tblTitle();
                    newTitle.name = oTitle.Name;
                    newTitle.isActive = oTitle.IsActive;
                    oEnts.tblTitles.AddObject(newTitle);
                    oEnts.SaveChanges();
                    success = true;
                }
            }
            catch (Exception e)
            {
                //..Log error
                success = false;
            }
            return success;
        }

        public bool Update(TitleModel oTitle)
        {
            bool success = false;
            try
            {
                using (MyEntities oEnts = new MyEntities())
                {
                    tblTitle editTitle = (from t in oEnts.tblTitles where t.id == oTitle.ID select t).FirstOrDefault();
                    if (editTitle == null)
                    {
                        success = false;
                        throw new NullReferenceException(String.Format("tblTitle entry with id = {0} does not exist to be updated.", oTitle.ID));
                    }
                    editTitle.name = oTitle.Name;
                    editTitle.isActive = oTitle.IsActive;
                    oEnts.SaveChanges();
                    success = true;
                }
            }
            catch (Exception e)
            {
                //..Log error
                success = false;
            }
            return success;
        }

        public bool UniqueName(string name, int id = 0)
        {
            int nameCount = 0;
            using (MyEntities oEnts = new MyEntities())
            {
                nameCount = (from t in oEnts.tblTitles where t.name == name && t.id != id select t).Count();
            }
            return (nameCount > 0) ? false : true;
        }
    }
}

Next we need our custom validation class.

using System.ComponentModel.DataAnnotations;
using MyProjectMVC3.BusinessLogic;
using MyProjectMVC3.Models;

namespace MyProjectMVC3.CustomValidators
{
    public class UniqueTitle : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            TitleModel oTitle = (TitleModel)validationContext.ObjectInstance;
            TitleBL blTitle = new TitleBL();
            if (blTitle.UniqueName(oTitle.Name, oTitle.ID))
            {
                return null;
            }
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }
    }
}

Finally we have to revisit our model to add our custom validation to the field.

using System.ComponentModel.DataAnnotations;
using MyProjectMVC3.CustomValidators;

namespace MyProjectMVC3.Models
{

    public class TitleModel
    {
        [Key]
        public int ID { get; set; }

        [Required(AllowEmptyStrings = false, ErrorMessage = "You must provide the name/title for this Title entry.")]
        [StringLength(50)]
        [UniqueTitle (ErrorMessage = "The name/title must be unique.")]
        public string Name { get; set; }

        [Display(Name = "Is Active", ShortName = "Active")]
        public bool IsActive { get; set; }
    }
}

Of course we will need to edit our CSHTML to use a validation on that field, below is just the bit for the form.

@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>@ViewBag.Title</legend>

        @Html.HiddenFor(model => model.ID)

        <div class="div120">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="div200">
            @Html.EditorFor(model => model.Name)
        </div>
        <div class="div200">
            @Html.ValidationMessageFor(model => model.Name)
        </div>
        <br />
        <div class="div120">
            @Html.LabelFor(model => model.IsActive)
        </div>
        <div class="div200">
            @Html.EditorFor(model => model.IsActive)
        </div>
        <div class="div200">
            @Html.ValidationMessageFor(model => model.IsActive)
        </div>
        <br />
        <p>
            <input type="submit" id="btnSubmit" name="btnSubmit" class="button" value="Save" />
        </p>
    </fieldset>
}

When the validation goes to take place, it will call our custom validation class. The part that makes this work is where I cast the ValidationContext.ObjectInstance to the object I want to validate.  In this case it is a TitleModel object.  If you put a break point in the code there and drill into the validationContext object’s ObjectInstance you will see that is exactly what it is so we can cast it.  Now that we have the new values we can check for it being unique in the database.  Finally our custom validation class returns null if there is no error or the error message if there is an error.

About SheldonS

Web developer for over 15 years mainly with Microsoft technologies from classic ASP to .NET 4. Husband, father, and aspiring amateur photographer.

Posted on November 7, 2012, in MVC and tagged , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: