ASP.NET MVC part 8 Adding Validation to the Model


Adding Validation to the Model

In this section you’ll add validation logic to the Movie model, and you’ll ensure that the validation rules are enforced any time a user attempts to create or edit a movie using the application.

Keeping Things DRY

One of the core design tenets of ASP.NET MVC is DRY (“Don’t Repeat Yourself”). ASP.NET MVC encourages you to specify functionality or behavior only once, and then have it be reflected everywhere in an application. This reduces the amount of code you need to write and makes the code you do write much easier to maintain.

The validation support provided by ASP.NET MVC and Entity Framework Code First is a great example of the DRY principle in action. You can declaratively specify validation rules in one place (in the model class) and then those rules are enforced everywhere in the application.

Let’s look at how you can take advantage of this validation support in the movie application.

Adding Validation Rules to the Movie Model

You’ll begin by adding some validation logic to the Movie class.

Open the Movie.cs file. Add a using statement at the top of the file that references theSystem.ComponentModel.DataAnnotations namespace:

using System.ComponentModel.DataAnnotations;

The namespace is part of the .NET Framework. It provides a built-in set of validation attributes that you can apply declaratively to any class or property.

Now update the Movie class to take advantage of the built-in RequiredStringLength, and Range validation attributes. Use the following code as an example of where to apply the attributes.

public class Movie
{
    public int ID { get; set; }

    [Required(ErrorMessage = "Title is required")]
    public string Title { get; set; }

    [Required(ErrorMessage = "Date is required")]
    public DateTime ReleaseDate { get; set; }

    [Required(ErrorMessage = "Genre must be specified")]
    public string Genre { get; set; }

    [Required(ErrorMessage = "Price Required")]
    [Range(1, 100, ErrorMessage = "Price must be between $1 and $100")]
    public decimal Price { get; set; }

    [StringLength(5)]
    public string Rating { get; set; }
}

The validation attributes specify behavior that you want to enforce on the model properties they are applied to. The Required attribute indicates that a property must have a value; in this sample, a movie has to have values for the TitleReleaseDateGenre, and Price properties in order to be valid. The Range attribute constrains a value to within a specified range. The StringLength attribute lets you set the maximum length of a string property, and optionally its minimum length.

Code First ensures that the validation rules you specify on a model class are enforced before the application saves changes in the database. For example, the code below will throw an exception when the SaveChangesmethod is called, because several required Movie property values are missing and the price is zero (which is out of the valid range).

MovieDBContext db = new MovieDBContext();

Movie movie = new Movie();
movie.Title = "Gone with the Wind";
movie.Price = 0.0M;

db.Movies.Add(movie);
db.SaveChanges();        // <= Will throw validation exception

Having validation rules automatically enforced by the .NET Framework helps make your application more robust. It also ensures that you can’t forget to validate something and inadvertently let bad data into the database.

Here’s a complete code listing for the updated Movie.cs file:

using System;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }

        [Required(ErrorMessage = "Title is required")]
        public string Title { get; set; }

        [Required(ErrorMessage = "Date is required")]
        public DateTime ReleaseDate { get; set; }

        [Required(ErrorMessage = "Genre must be specified")]
        public string Genre { get; set; }

        [Required(ErrorMessage = "Price Required")]
        [Range(1, 100, ErrorMessage = "Price must be between $1 and $100")]
        public decimal Price { get; set; }

        [StringLength(5)]
        public string Rating { get; set; }
    }

    public class MovieDBContext : DbContext
    {
        public DbSet<Movie> Movies { get; set; }
    }
}

Validation Error UI in ASP.NET MVC

Re-run the application and navigate to the /Movies URL.

Click the Create Movie link to add a new movie. Fill out the form with some invalid values and then click theCreate button.

8_validationErrors

Notice how the form has automatically used a background color to highlight the text boxes that contain invalid data and has emitted an appropriate validation error message next to each one. The error messages match the error strings you specified when you annotated the Movie class. The errors are enforced both client-side (using JavaScript) and server-side (in case a user has JavaScript disabled).

A real benefit is that you didn’t need to change a single line of code in the MoviesController class or in theCreate.cshtml view in order to enable this validation UI. The controller and views you created earlier in this tutorial automatically picked up the validation rules that you specified using attributes on the Movie model class.

How Validation Occurs in the Create View and Create Action Method

You might wonder how the validation UI was generated without any updates to the code in the controller or views. The next listing shows what the Create methods in the MovieController class look like. They’re unchanged from how you created them earlier in this tutorial.

//
// GET: /Movies/Create

public ActionResult Create()
{
    return View();
}

//
// POST: /Movies/Create

[HttpPost]
public ActionResult Create(Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Movies.Add(movie);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(movie);
}

The first action method displays the initial Create form. The second handles the form post. The second Createmethod calls ModelState.IsValid to check whether the movie has any validation errors. Calling this method evaluates any validation attributes that have been applied to the object. If the object has validation errors, theCreate method redisplays the form. If there are no errors, the method saves the new movie in the database.

Below is the Create.cshtml view template that you scaffolded earlier in the tutorial. It’s used by the action methods shown above both to display the initial form and to redisplay it in the event of an error.

@model MvcMovie.Models.Movie
@{
    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>Movie</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.ReleaseDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.ReleaseDate)
            @Html.ValidationMessageFor(model => model.ReleaseDate)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Genre)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Genre)
            @Html.ValidationMessageFor(model => model.Genre)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Price)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Price)
            @Html.ValidationMessageFor(model => model.Price)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Rating)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Rating)
            @Html.ValidationMessageFor(model => model.Rating)
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Notice how the code uses an Html.EditorFor helper to output the <input> element for each Movie property. Next to this helper is a call to the Html.ValidationMessageFor helper method. These two helper methods work with the model object that’s passed by the controller to the view (in this case, a Movie object). They automatically look for validation attributes specified on the model and display error messages as appropriate.

What’s really nice about this approach is that neither the controller nor the Create view template knows anything about the actual validation rules being enforced or about the specific error messages displayed. The validation rules and the error strings are specified only in the Movie class.

If you want to change the validation logic later, you can do so in exactly one place. You won’t have to worry about different parts of the application being inconsistent with how the rules are enforced — all validation logic will be defined in one place and used everywhere. This keeps the code very clean, and makes it easy to maintain and evolve. And it means that that you’ll be fully honoring the DRY principle.

Adding Formatting to the Movie Model

Open the Movie.cs file. The System.ComponentModel.DataAnnotations namespace provides formatting attributes in addition to the built-in set of validation attributes. You’ll apply the DisplayFormat attribute and aDataType enumeration value to the release date and to the price fields. The following code shows theReleaseDate and Price properties with the appropriate DisplayFormat attribute.

         [DataType(DataType.Date)] 
        public DateTime ReleaseDate { get; set; }
       
        [DataType(DataType.Currency)] 
        public decimal Price { get; set; }

Alternatively, you could explicitly set a DataFormatString value. The following code shows the release date property with a date format string (namely, “d”). You’d use this to specify that you don’t want to time as part of the release date.

        [DisplayFormat(DataFormatString = "{0:d}")]
       public DateTime ReleaseDate { get; set; }

The following code formats the Price property as currency.

        [DisplayFormat(DataFormatString = "{0:c}")]
       public decimal Price { get; set; }

The complete Movie class is shown below.

public class Movie
{
    public int ID { get; set; }

    [Required(ErrorMessage = "Title is required")]
    public string Title { get; set; }

    [Required(ErrorMessage = "Date is required")]
    [DisplayFormat(DataFormatString = "{0:d}")]
    public DateTime ReleaseDate { get; set; }

    [Required(ErrorMessage = "Genre must be specified")]
    public string Genre { get; set; }

    [Required(ErrorMessage = "Price Required")]
    [Range(1, 100, ErrorMessage = "Price must be between $1 and $100")]
    [DisplayFormat(DataFormatString = "{0:c}")]
    public decimal Price { get; set; }

    [StringLength(5)]
    public string Rating { get; set; }
}

Run the application and browse to the Movies controller.

8_format_SM

In the next part of the series, we’ll review the application and make some improvements to the automatically generated Details and Delete methods.

Posted on 23 April 2011, in ASP.NET MVC. 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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s

%d bloggers like this: