ASP.NET MVC QuickStart 4: implement validation

… continued from part 3.

Objectives

In this Hands-On Lab, you will create functionality to add validation of members. In particular, you will:

  • Task 1: understand validation
  • Task 2: add convenience method to ModelState
  • Task 3: implement validation logic in model
  • Task 4: handle validation errors in controller

System requirements

You must have the following items to complete this lab:

  • Microsoft Visual Studio 2008 SP1 (professional edition)
  • Microsoft ASP.NET MVC 1.0

Prequisites

You must have the following skills to understand this lab:

  • Fundamental knowledge of software development in .NET 3.5
  • Some experience in ASP.NET web development

This lab builds further on the QuickStart 3 code.

Task 1: understand validation

At this moment, when the user enters an empty first name and/or last name, everything works. Of course, we know that these properties are mandatory, so we have to add some validation logic to make sure that the user can’t enter invalid information.

Due to the separation of concerns, it is straightforward that this validation checking has to be done in the model, and that the view is responsible for showing any validation messages to the user. So what should happen is that the controller uses the model to do validation and then it passes the error information to the view.

Now, how can the controller pass error information to the view? The way to do this is by registering the errors in ModelState, which is a temporary storage area. The steps are:

  • Controller asks model to do validation and if validation fails, it receives the list of validation errors from the model.
  • Controller registers the validation errors in ModelState, by calling the ModelState.AddModelError method for each error. This will add all errors to the ModelStateDictionary.
  • The view has access to this ModelStateDictionary and you can use Html helper methods to display the information:
    • Html.ValidationSummary: returns a list of validation errors.
    • Html.ValidationMessage: returns validation message for specific field.

Task 2: add convenience method to ModelState

The ModelState.AddModelError method only allows adding one validation error at a time, so first we will create an extension method called ModelState.AddModelErrors that allows us to add a collection of validation errors in one method call.

Right click the web project MvcApplication1, and select Add -> New folder, and call it Helpers. Then right click the Helpers folder and select Add -> Class, and call it ModelStateHelpers.cs. Write the extension method as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MvcApplication1.Models;
using System.Web.Mvc;
namespace MvcApplication1.Helpers
{
public static class ModelStateHelpers
{
public static void AddModelErrors(
this ModelStateDictionary modelState,
IEnumerable<RuleViolation> errors)
{
foreach (RuleViolation issue in errors)
{
modelState.AddModelError(
issue.PropertyName, issue.ErrorMessage);
}
}
}
}

Task 3: implement validation logic in model

Now we will create the validation logic in our model.

First we will create a new class that can hold information (property name and related error message) about a validation error. Add a new class called RuleValidation to the Models folder:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication1.Models
{
public class RuleViolation
{
public string ErrorMessage { get; private set; }
public string PropertyName { get; private set; }
public RuleViolation(string errorMessage)
{
ErrorMessage = errorMessage;
}
public RuleViolation(string errorMessage, string propertyName)
{
ErrorMessage = errorMessage;
PropertyName = propertyName;
}
}
}

A very convenient way of telling whether there were validation errors is by throwing exceptions. So add a new class called RuleException to the Models folder, which is a special kind of exception that will hold all validation errors that have occurred:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections.Specialized;
namespace MvcApplication1.Models
{
public class RuleException : Exception
{
public IEnumerable<RuleViolation>
RuleViolations { get; private set; }
public RuleException(IEnumerable<RuleViolation> ruleViolations)
{
RuleViolations = ruleViolations;
}
}
}

Now it’s time to do the actual validation. Add the following two methods to the Member class in the Models folder:

public bool IsValid
{
get { return (GetRuleViolations().Count() == 0); }
}
public IEnumerable<RuleViolation> GetRuleViolations()
{
if (String.IsNullOrEmpty(FirstName))
yield return new RuleViolation(
"First name is required", "FirstName");
if (String.IsNullOrEmpty(LastName))
yield return new RuleViolation(
"Last name is required", "LastName");
yield break;
}

To keep things simple we demand that first name and last name have to be filled in.

Finally, we have to apply these validation checks in the MemberService operations. Add the following code to the CreateMember method in MemberService class in the Models folder:

public static void CreateMember(Member member)
{
if (!member.IsValid)
throw new RuleException(member.GetRuleViolations());
// Set member id to next free id
member.ID = members.Max(m => m.ID) + 1;
// add member to collection
members.Add(member);
}

And do the same in the UpdateMember method:

public static void UpdateMember(Member member)
{
if (!member.IsValid)
throw new RuleException(member.GetRuleViolations());
// Find member in collection
Member foundMember = members.Find(m => m.ID == member.ID);
// Update member
foundMember.FirstName = member.FirstName;
foundMember.LastName = member.LastName;
}

So when a new member is added or updated, before the actual creation or update happens the member object is checked to see if it’s valid – if it isn’t, a RuleException is thrown to the caller (which is the controller) that has to handle it.

Task 4: handle validation errors in controller

Modify code of the Create action method in the MembersController class as follows:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Member member)
{
try
{
// Use model to create this new member
MemberService.CreateMember(member);
// Redirect to Details action method and pass the new id
return RedirectToAction("Details", new { id = member.ID });
}
catch (RuleException ex)
{
ModelState.AddModelErrors(ex.RuleViolations);
return View(member);
}
}

And do the same for the Update action method:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Member member)
{
try
{
// Use model to create this new member
MemberService.CreateMember(member);
// Redirect to Details action method and pass the new id
return RedirectToAction("Details", new { id = member.ID });
}
catch (RuleException ex)
{
ModelState.AddModelErrors(ex.RuleViolations);
return View(member);
}
}

Note: The method ModelState.AddModelErrors is the extension method that we wrote earlier, so in order to use it we also have to add a using statement on top of the MembersController class:

using MvcApplication1.Helpers;

Right, let’s test this. Start the web application, and create a new member but don’t fill in last name:

My MVC application

Click Create.

My MVC application

The validation does its work and validation errors are displayed. But why does it also display the ‘A value is required’ message?

Actually, except for the manual validation messages that you add yourself, ASP.NET MVC also does its own validation checks on properties. There is a validation of all bound properties of a member object, which are FirstName, LastName but also… ID – because default, all properties of an object are bound to the view, even if this property is not used in the view. In our case, the ID is of type int, and therefore it cannot be null; so the validation message is added.

To solve this, we have to tell MVC not to bind the ID property, because it’s our responsibility to generate it. You can do this by decorating the Member class with the Bind attribute as follows:

[Bind(Include = "FirstName,LastName")]
public class Member
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }

}

Now only FirstName and LastName properties are bound and validated; and so ID is not validated anymore:

My MVC application

Go to next part.
About these ads

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

Follow

Get every new post delivered to your Inbox.