ASP.NET MVC Application Building: Forums #4 1/2– Validation Revisited

I received a lot of feedback on my last blog entry on implementing server-side form validation for the MVC Forums application. Thank you everyone for the feedback!

In my previous blog entry, I described how you can perform validation in an MVC application by taking advantage of custom validation attributes. By decorating your model with attributes, such as the RequiredValidator and RegularExpressionValidator attributes, you can enforce validation rules and display custom validation error messages.

In order to perform more complex validation, I suggested using a CustomValidator that could be applied to the entity class. The CustomValidator class points to another class that executes custom validation rules.

One piece of feedback that I consistently received was that this approach for performing custom validation was too complex for most scenarios. Most people liked the attribute approach to validation, but also wanted an easy way to do custom validation.

Therefore, in this blog entry, I take a slightly different approach to handling the problem of form validation. In this blog entry, I combine the approach described by Scott Guthrie with an attribute approach. In other words, I mix an imperative and declarative approach to validation. I call this approach a hybrid approach to form validation.

In this blog entry, I describe the hybrid approach. Before we get into the details, I should mention that I just came across an excellent blog entry by Steve Sanderson that describes almost exactly the same approach to validation described in this blog entry (however — his blog entry has much nicer diagrams than mine). You can view his post here:

http://blog.codeville.net/2008/09/08/thoughts-on-validation-in-aspnet-mvc-applications/

An Imperative Approach to Form Validation

Let’s start with the approach described by Scott Guthrie. When following this approach to form validation, you must create the following classes:

· IRuleEntity – an interface that each entity must implement in order to be validated.

· RuleViolation – a class that represents a validation error.

· RuleViolationException – an exception that is raised when an entity fails validation.

· Validation – a class that includes a method for copying rule violations to ModelState.

The IRuleEntity interface is really simple. It consists of two methods named Validate() and GetRuleViolations(). This interface is contained in Listing 1.

Listing 1 – IRuleEntity.cs

using System.Collections.Generic;

namespace MvcValidation
{
    public interface IRuleEntity
    {
        List<RuleViolation> GetRuleViolations();
        void Validate();
    }
}

The RuleViolation class represents a particular validation error. A collection of RuleViolations is used to represent all of the validation errors that result when submitting a form. The RuleViolation class is contained in Listing 2.

Listing 2 – RuleViolation.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MvcValidation
{
    public class RuleViolation
    {
        public string PropertyName { get; private set; }

        public object PropertyValue { get; private set; }

        public string ErrorMessage { get; private set; }

        public RuleViolation(string propertyName, object propertyValue, string errorMessage)
        {
            this.PropertyName = propertyName;
            this.PropertyValue = propertyValue;
            this.ErrorMessage = errorMessage;
        }

    }
}

When there is at least 1 rule violation, a RuleViolationException is raised. This special exception class is contained in Listing 3.

Listing 3 – RuleViolationException.cs

using System;
using System.Collections.Generic;

namespace MvcValidation
{
    public class RuleViolationException : Exception
    {
        public RuleViolationException(string message, List<RuleViolation> validationIssues)
            : base(message)
        {
            this.ValidationIssues = validationIssues;
        }

        public List<RuleViolation> ValidationIssues { get; private set; }
    }
}

Finally, the Validation class is a utility class that exposes one method named UpdateModelStateWithViolations(). This method copies all of the rule violations to a controller class’s ModelState. The Validation class is contained in Listing 4.

Listing 4 – Validation.cs

using System.Web.Mvc;

namespace MvcValidation
{
    public class Validation
    {
        public static void UpdateModelStateWithViolations(RuleViolationException ruleViolationException, ModelStateDictionary modelState)
        {
            foreach (var issue in ruleViolationException.ValidationIssues)
            {
                var value = issue.PropertyValue ?? string.Empty;
                modelState.AddModelError(issue.PropertyName, value.ToString(), issue.ErrorMessage);
            }
        }


    }
}

Let’s take a look at how all of these classes work together. The Message class in Listing 5 represents a Forum message. Notice that this class implements the IRuleEntity interface by implementing both the Validate() and the GetRuleViolations() methods.

Listing 5 – Message.cs

using System;
using MvcValidation;
using System.Collections.Generic;
using System.Data.Linq;
using MvcValidation.Attributes;
using System.Web.Mvc;

namespace MvcForums.Models.Entities
{
    public class Message :IRuleEntity
    {
        private DateTime _entryDate = DateTime.Now;

        public Message()
        { }

        public Message(int id, int? parentThreadId, int? parentMessageId, string author, string subject, string body)
        {
            this.Id = id;
            this.ParentThreadId = parentThreadId;
            this.ParentMessageId = parentMessageId;
            this.Author = author;
            this.Subject = subject;
            this.Body = body;
        }

        public int Id { get; set; }
        public int? ParentThreadId { get; set; }
        public int? ParentMessageId { get; set; }
        public string Author { get; set; }
        public string Subject { get; set; }
        public string Body { get; set; }


        public DateTime EntryDate
        {
            get { return _entryDate; }
            set { _entryDate = value; }
        }

        #region IRuleEntity Members

        public List<RuleViolation> GetRuleViolations()
        {
            var validationIssues = new List<RuleViolation>();

            // Validate Subject
            if (String.IsNullOrEmpty(this.Subject))
                validationIssues.Add(new RuleViolation("subject", this.Subject, "You must enter a message subject."));

            // Validate Body
            if (String.IsNullOrEmpty(this.Body))
                validationIssues.Add(new RuleViolation("body", this.Body, "You must enter a message body."));

            return validationIssues;
        }

        public void Validate()
        {
            var validationIssues = GetRuleViolations();
            if (validationIssues.Count > 0)
                throw new RuleViolationException("Validation Issues", validationIssues);
        }

        #endregion

    }
}

The Validate() method calls the GetRuleViolations() method. If there are any validation rule violations then the Validate() method throws a RuleViolationException.

In the Forums application, the Validate() method is called within the ForumRepository. Before a new Message is added to the database, the Validate() method is called on the Message class like this:

public Message AddMessage(Message messageToAdd)

{

  messageToAdd.Validate();

  _dataContext.Insert(messageToAdd);

  return messageToAdd;

}

If there is a validation error then a RuleViolationException is thrown and the message never gets inserted into the database.

The ForumRepository is used within the Forum controller. When you submit a form for creating a new forum message, the ForumController.Create(FormCollection collecction) method is called. The Forum controller is contained in Listing 6.

Listing 6 – ForumController.cs

using System;
using System.Web.Mvc;
using MvcForums.Models;
using Microsoft.Web.Mvc;
using MvcForums.Models.Entities;
using MvcValidation;

namespace MvcForums.Controllers
{
    public class ForumController : Controller
    {
        private IForumRepository _repository;

        public ForumController()
            : this(new ForumRepository())
        { }

        public ForumController(IForumRepository repository)
        {
            _repository = repository;
        }

        public ActionResult Index()
        {
            ViewData.Model = _repository.SelectThreads();
            return View("Index");
        }

        [AcceptVerbs("GET")]
        public ActionResult Create()
        {
            return View("Create");
        }

        [AcceptVerbs("Post")]
        public ActionResult Create(FormCollection form)
        {
            var messageToCreate = new Message();

            try
            {
                UpdateModel(messageToCreate, new[] { "Author", "ParentThreadId", "ParentMessageId", "Subject", "Body" });
                _repository.AddMessage(messageToCreate);
            }
            catch (RuleViolationException rex)
            {
                Validation.UpdateModelStateWithViolations(rex, ViewData.ModelState);
                return View("Create", messageToCreate);
            }
            catch (Exception ex)
            {
                ViewData.ModelState.AddModelError("message", null, "Could not save message.");
                return View("Create", messageToCreate);
            }

            // Redirect
            return RedirectToAction("Index");
        }

        public ActionResult Thread(int threadId)
        {
            ViewData.Model = _repository.SelectMessages(threadId);
            return View("Thread");
        }

    }
}

Notice that there are two Create() methods. The first Create() method is invoked only for an HTTP GET operation. This action returns the form for creating a new Message (see Figure 1).

Figure 1 – Form for adding a Forums message

image

The second Create() method is invoked when an XHTML form is posted to the server. When the form data is posted, the Create() method calls the Controller.UpdateModel() method to generate a Product class that has all of the values submitted in the form. Next, the ForumRepository.Add() method is called to add the new instance of the Product class to the database.

Both statements are wrapped in a Try..Catch block. If either the Controller.UpdateModel() or the ForumRepository.Add() methods fail then the Catch clause of the Try…Catch statement is executed. The first Catch clause updates the ViewData.ModelState property with information about the validation errors by copying the validation errors from the RuleViolationException to ModelState. Next, the Create view is redisplayed.

Notice that there are two Catch clauses. The second Catch clause captures a generic Exception. This Catch clause is invoked when the UpdateModel() method encounters a validation issue (the wrong type of value is being assigned to a property). This clause will be invoked when there are network problems communicating with your database server or your database server crashes. Notice that a error key named message is updated.

The Create view is contained in Listing 7.

Listing 7 — Create.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Create.aspx.cs" Inherits="MvcForums.Views.Forum.Create" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
    <style type="text/css">

    .input-validation-error
    {
        border: solid 2px red;
    }

    #message
    {
        padding: 5px;
        color:red;
    }

    </style>
</head>
<body>
    <div>

    <form method="post" action="/Forum/Create">

    <div id="message">
    <%= Html.ValidationMessage("message") %>
    </div>

    <input name="author" type="hidden" value="Stephen" />

    <label for="subject">Subject:</label>
    <%= Html.TextBox("subject") %>
    <%= Html.ValidationMessage("subject") %>

    <br /><br />
    <label for="body">Body:</label>
    <%= Html.TextArea("body") %>
    <%= Html.ValidationMessage("body") %>

    <br /><br />
    <input type="submit" value="Post" />

    </form>

    </div>
</body>
</html>

 

Notice that the view includes a generic error message (named message).

 

Figure 2 — Generic error message

image

 

This approach to performing validation is an imperative approach. The validation rules are expressed by imperative code in the GetRuleViolations() method like this:

public List<RuleViolation> GetRuleViolations()
{
    var validationIssues = new List<RuleViolation>();

    // Validate Subject
    if (String.IsNullOrEmpty(this.Subject))
        validationIssues.Add(new RuleViolation("subject", this.Subject, "You must enter a message subject."));

    // Validate Body
    if (String.IsNullOrEmpty(this.Body))
        validationIssues.Add(new RuleViolation("body", this.Body, "You must enter a message body."));

    return validationIssues;
}

Using an imperative approach you can perform any type of validation that you need. For example, you can perform a database lookup to make sure that the Message subject and body are unique in the database before inserting the Message into the database.

A Declarative Approach to Form Validation

I am a lazy person. I like a declarative approach to performing form validation. A declarative approach enables you to validate form data without writing any code.

Another advantage of a declarative approach is that a declarative approach enables you to easily implement client-side validation. We want to generate client-side validators from our server-side validators. That way, we can validate a form without posting the form back to the server.

The modified Message class in Listing 8 uses a declarative approach to validation.

Listing 8 – Message.cs (with attributes)

using System;
using MvcValidation;
using System.Collections.Generic;
using System.Data.Linq;
using MvcValidation.Attributes;
using System.Web.Mvc;

namespace MvcForums.Models.Entities
{
    public class Message :IRuleEntity
    {
        private DateTime _entryDate = DateTime.Now;

        public Message()
        { }

        public Message(int id, int? parentThreadId, int? parentMessageId, string author, string subject, string body)
        {
            this.Id = id;
            this.ParentThreadId = parentThreadId;
            this.ParentMessageId = parentMessageId;
            this.Author = author;
            this.Subject = subject;
            this.Body = body;
        }

        public int Id { get; set; }
        public int? ParentThreadId { get; set; }
        public int? ParentMessageId { get; set; }
        public string Author { get; set; }

        [RequiredValidator("You must enter a message subject.")]
        public string Subject { get; set; }

        [RequiredValidator("You must enter a message body.")]
        public string Body { get; set; }


        public DateTime EntryDate
        {
            get { return _entryDate; }
            set { _entryDate = value; }
        }

        #region IRuleEntity Members

        public List<RuleViolation> GetRuleViolations()
        {
            var validationIssues = new List<RuleViolation>();

        // Validate attributes
            AttributeValidation.Validate(this, validationIssues);

            return validationIssues;
        }

        public void Validate()
        {
            var validationIssues = GetRuleViolations();
            if (validationIssues.Count > 0)
                throw new RuleViolationException("Validation Issues", validationIssues);
        }

        #endregion

    }
}

Notice that both the Subject and Body properties in the modified Message class are decorated with the RequiredValidator attribute. The RequiredValidator attribute marks both of these properties as required.

Notice, furthermore, that the imperative code has been removed from the GetRuleViolations() method. Instead, the AttributeValidation.Validate() method is called. This method calls the Validate() method on each validator attribute applied to the Message class. If at least one validator fails then the Message class fails validation and a RuleViolationException is thrown.

Clearly, you can mix both an attribute and non-attribute approach to validation. For common validation tasks, you can use standard validators such as the RequiredValidator , LengthValidator, or RegularExpressionValidator. For more complicated types of validation, you can write imperative code to perform the validation.

Summary

In this blog entry, I’ve described an approach to validation that combines imperative and declarative validation. I believe that the easiest approach for common validation tasks is a declarative approach. For more complex or specialized validation tasks, write imperative validation code. In the case of the Forums application, my plan is to take a hybrid approach to validation.

Discussion

  1. http:// says:

    Yes, good decision to use the hybrid approach as it recognises that developers will be using the DataAnnotations attributes with Dynamic Data. A helper method AttributeValidation.Validate() would be great until the model is updated with more complex validation.

  2. Hey, looks good. A couple of suggestions:

    [1] I don’t think you actually need the IRuleEntity interface. This is one of the things I was trying to avoid in my approach to validation – model objects shouldn’t have to advertise the fact that they do validation, they just do it. Looking at your code, I think you can remove IRuleEntity and it would just continue working. There’s no need to define or implement this interface.

    [2] I think there’s a mistake in listing 6. You say “If either the Controller.UpdateModel() … fail then the Catch clause of the Try…Catch statement is executed”, but you’re only catching a RuleViolationException, and UpdateModel() doesn’t throw that type of exception. Or have I misunderstood what you’re doing? This looks like the same issue with setter exceptions that I was writing about, and have asked the MVC team to fix at http://www.codeplex.com/…/View.aspx?WorkItemId=2255.

  3. http:// says:

    I read Stephen Walter’s article earlier today and liked what I saw. Great seeing even more discussion surrounding a similar approach!

    One, tiny, thing I personally would alter is to make the UpdateModelStateWithViolations method an extension method to the ModelStateDictionairy (or atleast add an extension that wraps Validator.UpdateModelStateWithViolations) to make the code more fluent

    public static void UpdateModelStateWithViolations(this ModelStateDictionary modelState, RuleViolationException ruleViolationException)
    {
    foreach (var issue in ruleViolationException.ValidationIssues)
    {
    var value =
    issue.PropertyValue ?? string.Empty;
    modelState.AddModelError(issue.PropertyName, value.ToString(), issue.ErrorMessage);
    }
    }

    Would enable you to call it

    ViewData.ModelState.UpdateModelStateWithViolations(…) instead

  4. http:// says:

    Erm, I did of course mean the blog of Steve Sanderson ;)

  5. http:// says:

    Also about the IRuleEntity interface. Like Steven says, its not needed here. The only time you’d really need it is if you treated your entities in a polymorphic fashion and that’s not needed in a lot of cases, especially not here.

  6. http:// says:

    I belive my other post got lost. The blog i was refering to was of couse the one of Steve Sanderson. I also mentioned that the IRuleEntity isn’t needed unless the entities are treated in a polymorphic fashion.

  7. http:// says:

    I don’t mind IRuleEntity as you could more easily have an RuleEntityModelBinder populating ModelState. Or use IDataErrorInfo or other interface, which Scott Guthrie in his Preview 5 comment replies suggested might get some support.

  8. http:// says:

    What about type validation? if the user enters “one” for a price field instead of “1″, how is this validated? Wouldn’t it throw conversion exception or something?

  9. BringerOD says:

    This will fit many more scenarios.

  10. http:// says:

    @SteveSanderson – Thanks for catching the mistake in the ForumController — I fixed the code in the listing.

  11. Mike Hadlow says:

    I don’t like it. Call me old-fashioned, but I really like to have validation expressed in the domain entity itself. It seems a more DDD friendly way of doing things. Property setters, or constructor arguments, should throw exceptions if the values passed to them fail validation rules. Entities should enforce business rules rather than relying on convention to make them work. With this method it’s easy to set an incorrect value on an entity. You have to explicitly invoke your framework in order for the attributes to be evaluated.

    More here:
    mikehadlow.blogspot.com/…/…ork-validation.html

  12. http:// says:

    @Andreas Håkansson — the extension method on ModelState is a great idea! Very clean solution.

  13. http:// says:

    I like this set-up, nice and clean. I can has code ?

  14. http:// says:

    Dear Stephen,

    Just want to ask if AttributeValidation.Validate() you used in the last portion of the code from MvcValidation, the project found on Google Code?

    Thanks,
    Ray.

  15. Hi Stephen,

    Stephen, sorry my bad english.
    Please, you can help me?
    I develop one open source application to payment control (in brasilian portuguese) and use ASP.NET MVC. ASP.NET MVC is very good and today I stop in a problem.

    My source code is in http://www.codeplex.com/meudindin

    I have one View “Painel” and this view render two UserControls to input form: “NovaConta.ascx” (new account) and “NovaAgenda.ascx” (new schedule payment)

    This forms (New Account and New Schedule Payment) is in the same View “Painel”. Html.RenderPartial(“NovaConta”) and Html.RenderPartial(“NovaAgenda”). In my UserControls write < %= Html.ValidationSummary("conta") %> and < %= Html.ValidationSummary("agenda")%> to show Summary errors

    I put the validation explaned in this post in my application but:
    IF IN EMPTY FORM (or invalid input entries in form) and click in “Submit Button”, the message of the Html.ValidationSummary is show in my two Forms.

    See yourself in http://www.meudindin.com/inicio/login EMAIL teste@teste.com PASSWORD teste
    Login in application and click in link “adicionar nova conta pessoal” later click in submit button “criar”

    Why this happen?

    Thank´s

    []´s

  16. gerry lowry says:

    “re: ASP.NET MVC Application Building: Forums #4 1/2– Validation Revisited”

    so, pray tell, where is 2/2 to be found, as in
    “re: ASP.NET MVC Application Building: Forums #4 2/2– Validation Revisited”

    the simple cheat
    stephenwalther.com/…/…alidation-revisited.aspx
    bounces me to http://stephenwalther.com/blog/.

    Please advise.

    Thank you.

    Regards,
    Gerry (Lowry)

  17. still not getting you…sorry. Thanks.

  18. Ya really great one. It will help me a lot. Thanks.

  19. n earlier ASP.NET MVC previews, form validation was something that should be implemented “by hand”. Since the new ASP.NET MVC preview 5, form validation has become more handy. Let me show you how you can add validation in such a ridiculously.

  20. Really great one! This will help me a lot. Thanks Stephen for sharing this useful blog.

  21. Smith says:

    Fantastic instruction and graphics. Kudos on this innovation.online masters degree | PhD history

  22. spatthi says:

    Can you please tell me how we can validate controls inside partial view. What ever the validation I have done in partial view is not firing when I click submit button

  23. spatthi says:

    I m very much new to Asp.net MVC and Xval framework.I m validating partial view with dataannotations attributes and calling it client side by using Xval.but its not getting fired.Can you please tell me if I have a usercontrol which will be generated ‘N’ no.of time on view and want to validate those usercontrols when I click submit.So I can validate in such situations

  24. Really great one. It will help me a lot. Thanks.

  25. 34 It looks like DataContextExtensions.cs line 45 of the Save method should pass the primaryKeyName through to Update.23r

  26. Nathanial says:

    I really like to have validation expressed in the domain entity itself. It seems a more DDD friendly way of doing things. Property setters, or constructor arguments, should throw exceptions if the values passed to them fail validation rules.

    toronto swingers club |toronto web design |toronto web hosting