Data validation

Contents:

  1. Introduction
  2. Adding validation errors directly to the ModelState
  3. Applying validation attributes
  4. Implementing model validation with IDataErrorInfo
  5. Implementing model validation with IValidatableObject
  6. Implementing client side validation
  7. Implementing remote validation
  8. Summary

1. Introduction

This is the second post in a series of articles about ASP.NET MVC 4. In this post I will use the previous demo application as the starting point and add data validation to the contact us form. You can download the source code for the previous post from Codeplex.

There are several ways to implement validation:

  1. Adding validation errors directly to the ModelState – in this case the validation happens in the controller where we check the posted data and, if the validation fails, we add the validation errors directly to the ModelState object. The ModelState is a property of the controller class that holds all the model-binding validation errors along with the raw values that were submitted to the server. Although this can seem to be the easiest method to apply validation it is the least recommended one because, according to the MVC pattern, the best place to implement data validation is at the model level since the model is responsible for defining the domain data and the business logic to apply on that data while the controller should implement only the application flow.
  2. Using DataAnnotation attributes – we can decorate the model properties using attributes that define common validation checks, for example we can mark a field as required or as having a given range of values. These attributes are defined in the System.ComponentModel.DataAnnotations namespace. When applied to a model property they will automatically check the value of the property and, in case of errors, will add them to the ModelState.
  3. Implementing the IDataErrorInfo interface in the model – this was the initial way of performing custom validation in MVC 1.0. Although it is still completely functional it is recommended that we use the IValidatableObject interface instead.
  4. Implementing the IValidatableObject interface in the model – IValidatableObject it’s a newer version of IDataErrorInfo used for server-side class-level validation and currently the recommended way of implementing complex server side validation that can target more than only one field.
  5. Client-side validation – some of the server side validation attributes provide also implicit client side validation. We will have a look on how to enable client-side validation.
  6. Remote Validation – allows us to make asynchronous calls to the server in order to validate the user input.

Next I will try to give an example for each validation method using the ‘Contact Us’ form that we created in the previous tutorial. This is what we did until now:

  1. We used Entity Framework (EF) to generate the data access classes for Messages and ContactReasons.The DB structure
  2. We created a new file under the Models folder where we added an additional partial class for the Message class that was generated by EF.
  3. In the same file we created an associated metadata class, called MessageMetaData, that we used to add attributes to some of the properties defined in the Message class. We need this additional class because, if we add attributes directly to the properties generated by EF, then, the next time the EF model will be modified/refreshed, the class would be recreated from scratch and our modifications would be lost.
  4. We linked the class MessageMetaData with our partial definition of the Message class using the MetadataTypeAttribute. In this moment our model definition looks like this:
    namespace HelloWorld.Code.DataAccess
    {
        [MetadataType(typeof(MessageMetaData))]
        public partial class Message
        {
           //Code omitted for brevity
        }
    
        public class MessageMetaData
        {
            [Display(Name = "E-mail")]
            public string Email { get; set; }
    
            [Display(Name = "Contact reason")]
            public int ContactReasonId { get; set; }
    
            [Display(Name = "Message")]
            public int Message1 { get; set; }
        }
    }
    

Now let’s add the following validation rules:
Contact Us Validation

  • the name is mandatory and it must be a string of at least 3 characters and at most 50 characters,
  • the e-mail is mandatory and must be a valid e-mail address with no more than 50 characters,
  • the user must select a contact reason,
  • the subject must be a string of at most 100 characters,
  • the message is required.

2. Adding validation errors directly to the ModelState

We will change the controller method that handles the HttpPost to include model validation and save the data only if the validation passed:

[HttpPost]
public ActionResult ContactUs(Code.DataAccess.Message message)
{
	//Name
	if(string.IsNullOrEmpty(message.Name))
		ModelState.AddModelError("Name", "Required");
	else if(message.Name.Length ❤ || message.Name.Length > 50)
		ModelState.AddModelError("Name", "The name must be a string of at least 3 characters and at most 50 characters");

	//Email
	if (string.IsNullOrEmpty(message.Email))
		ModelState.AddModelError("Email", "Required");
	else if (message.Email.Length > 50)
		ModelState.AddModelError("Email", "The e-mail can't have more than 50 characters");
	else if (!Regex.IsMatch(message.Email, @"^\w+([-+.]*[\w-]+)*@(\w+([-.]?\w+)){1,}\.\w{2,4}$"))
		ModelState.AddModelError("Email", "Invalid e-mail");

	//ContactReasonId
	if(!message.ContactReasonId.HasValue)
		ModelState.AddModelError("ContactReasonId", "Required");

	//Subject
	if (string.IsNullOrEmpty(message.Subject))
		ModelState.AddModelError("Subject", "Required");
	else if (message.Subject.Length > 100)
		ModelState.AddModelError("Subject", "The subject must be a string with a maximum length of 100 characters");

	//Message
	if (string.IsNullOrEmpty(message.Message1))
		ModelState.AddModelError("Message1", "Required");

	if (ModelState.IsValid)
	{
		string saveResultMessage;
		ViewBag.SaveResult = message.Save(out saveResultMessage);
		ViewBag.SaveResultMessage = saveResultMessage;
	}
	return View(message);
}

Now, in order to display the validation errors, we will add into the view a validation element @Html.ValidationMessageFor(model => model.[[FieldName]]) for each of the fields in our model:

@model HelloWorld.Code.DataAccess.Message
@{
    ViewBag.Title = "Contact Us";
}
<h2>Contact Us</h2>
<hr />
@using (Html.BeginForm("ContactUs", "Home", FormMethod.Post))
{
    @Html.LabelFor(model => model.Name, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Name)
    @Html.ValidationMessageFor(model => model.Name)

    @Html.LabelFor(model => model.Email, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Email)
    @Html.ValidationMessageFor(model => model.Email)

    @Html.LabelFor(model => model.ContactReasonId, new { @class = "control-label" })
    @Html.DropDownListFor(model => model.ContactReasonId, Model.ContactReasonsList)
    @Html.ValidationMessageFor(model => model.ContactReasonId)

    @Html.LabelFor(model => model.Subject, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Subject)
    @Html.ValidationMessageFor(model => model.Subject)

    @Html.LabelFor(model => model.Message1, new { @class = "control-label" })
    @Html.TextAreaFor(model => model.Message1)
    @Html.ValidationMessageFor(model => model.Message1)

    <input type="submit" name="Save" value="Save" />
}
@if (ViewBag.SaveResult != null && ViewBag.SaveResult)
{
    <span style="color: green;">@ViewBag.SaveResultMessage</span>
}
@if (ViewBag.SaveResult != null && !ViewBag.SaveResult)
{
    <span style="color: red;">@ViewBag.SaveResultMessage</span>
}

I also modified a little the CSS in “~/Shared/_Layout.cshtml” in order to color the error messages in red:

            body {
                 margin:0; 
                 padding:0;
            }
            #header_container {
                 background: black; 
                 color: white; 
                 height:60px; 
                 left:0; 
                 position:fixed; 
                 width:100%; 
                 top:0;
            }
            #header {
                 line-height:60px; 
                 margin:0 auto; 
                 padding-left: 50px; 
                 font-weight: bold;
                 font-size: 18pt;
            }
            #container {
                 margin:0 auto; 
                 overflow:auto; 
                 padding:80px 0; 
                 width:940px;
            }
            #container input[type=text], select, textarea  
            {
                width: 200px;
                margin: 2px;
                box-sizing: border-box;
                -moz-box-sizing: border-box;
                -webkit-box-sizing: border-box;
            }
            .control-label {
                width: 100px;
                display: inline-block;
                vertical-align: top;
            }
            .field-validation-error {
                color: red;
                vertical-align: top;
                padding-left: 5px;
            }
            .validation-summary-errors {
                color: red;
            }

If you run the site you will notice that the validation is applied. Anyway the code above is not nice, we repeated the required check a lot of times which contradicts with the DRY (Don’t Repeat Yourself) principle and, even worse, we placed model specific rules inside the controller. We will improve this in the next section by moving the validation inside the model and applying validation attributes.

3. Applying validation attributes

The System.ComponentModel.DataAnnotations namespaces contains a set of attributes that provide common validation capabilities. We will apply them in the MessageMetaData class in the same way we applied the [Display(“…”)] attribute for changing the UI text associated with a property.

public class MessageMetaData
{
	[Required(ErrorMessage = "Required")]
	[StringLength(50, MinimumLength = 3, ErrorMessage = "The name must be a string of at least 3 characters and at most 50 characters")]
	public string Name { get; set; }

	[Required(ErrorMessage = "Required")]
	[StringLength(50, ErrorMessage = "The e-mail can't have more than 50 characters")]
	[RegularExpression(@"^\w+([-+.]*[\w-]+)*@(\w+([-.]?\w+)){1,}\.\w{2,4}$", ErrorMessage = "Invalid e-mail")]
	[Display(Name = "E-mail")]
	public string Email { get; set; }

	[Required(ErrorMessage = "Required")]
	[Display(Name = "Contact reason")]
	public int? ContactReasonId { get; set; }

	[Required(ErrorMessage = "Required")]
	[StringLength(100, ErrorMessage = "The subject must be a string with a maximum length of 100 characters")]
	public string Subject { get; set; }

	[Required(ErrorMessage = "Required")]
	[Display(Name = "Message")]
	public string Message1 { get; set; }
}

And, of course, we will remove the previous code we added in the controller for managing validation:

[HttpPost]
public ActionResult ContactUs(Code.DataAccess.Message message)
{
	if (ModelState.IsValid)
	{
		string saveResultMessage;
		ViewBag.SaveResult = message.Save(out saveResultMessage);
		ViewBag.SaveResultMessage = saveResultMessage;
	}
	return View(message);
}

4. Implementing model validation with IDataErrorInfo

When Entity Framework generates an entity class, it automatically creates two partial methods for each property of the class:

  1. OnChanging method, that is called right before the corresponding property is changed and
  2. OnChanged method, that is called right after the property is changed.

So, for the models that are based on Entity Framework, we can add validation for each individual field by implementing the partial method that handles the changes for that data field. For example, we will add validation for the Name field making sure that the user entered at least two words (name and surname). We will provide an implementation for the partial method OnNameChanging inside the partial class definition that we created inside the Models folder.

[MetadataType(typeof(MessageMetaData))]
public partial class Message
{
	#region Validation

	partial void OnNameChanging(string value)
	{
		if(string.IsNullOrEmpty(value) || value.Split(new []{" "}, StringSplitOptions.RemoveEmptyEntries).Length < 2)
			throw new ValidationException("Please enter your name and surname");
	}

	#endregion

	//code removed for brevity
}

At this point, if we run the site, we will notice that the validation is applied but the error message is different than the one we’ve set in the ValidationException:
Using ValidationExceptionIn order to pass the validation messages from the model to the user interface we will implement in the model the IDataErrorInfo interface.

[MetadataType(typeof(MessageMetaData))]
public partial class Message : IDataErrorInfo
{
	#region Validation

	partial void OnNameChanging(string value)
	{
		if(string.IsNullOrEmpty(value) || value.Split(new []{" "}, StringSplitOptions.RemoveEmptyEntries).Length < 2)
			_errors.Add("Name", "Please enter your name and surname");
	}

	#region IDataErrorInfo Members

	public string Error { get; private set; }

	private readonly Dictionary<string, string> _errors = new Dictionary<string, string>();
	public string this[string columnName]
	{
		get
		{
			return _errors.ContainsKey(columnName) ? _errors[columnName] : string.Empty;
		}
	}

	#endregion

	#endregion

	//Code omitted for brevity
}

Now we are able to see the correct error message because, when the posted form is bound to the model, the model binder checks to see if the model class implements the IDataErrorInfo interface. If it does, then it will invoke the IDataErrorInfo.this indexer for each property of the class in order to check for errors and add them to the ModelState dictionary. The model binder will also check the value of the property IDataErrorInfo.Error. This property can be used to set validation errors that are not specific to any property. We can use it to enforce complex validation rules that target multiple fields in the model.

Although IDataErrorInfo is completely supported in MVC 4 it is recommended that we use IValidatableObject which is a newer interface that was introduced starting with .NET framework 4.0.

5. Implementing model validation with IValidatableObject

This interface contains only one method IEnumerable Validate(ValidationContext validationContext) that is supposed to contain all the validation rules that a model needs to pass. Note that the model binder will call the Validate method only if there are no errors in the model’s properties.
We will extend our model class to include 2 new validation rules:
– The subject and the message can’t contain the same text,
– We can’t add a new message if we already have in the DB another message with the same subject and from the same user.

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
	//Thow an error if the subjct and the message are the same
	if (string.Compare(Subject.Trim(), Message1.Trim(), true) == 0)
	{
		yield return new ValidationResult("The subject and the message can't be the same.");
	}

	//Throw an error there is another message with the same subject from the same person
	using (var context = new MvcDemoEntities())
	{
		var existingMessage = context.Messages.FirstOrDefault(m => string.Compare(m.Name.Trim(), Name.Trim(), StringComparison.InvariantCultureIgnoreCase) == 0 &&
																   string.Compare(m.Subject.Trim(), Subject.Trim(), StringComparison.InvariantCultureIgnoreCase) == 0);
		if(existingMessage != null)
		{
			yield return new ValidationResult("A message with the same subject was previously saved in the DB.", new []{"Subject"});
		}
	}
}

The IValidatableObject.Validate() method can apply validation rules that target multiple properties, and can yield back multiple validation errors. Each ValidationResult object contains an optional list of property names that caused the rule violation, which will be used when displaying the error messages in the UI. In our example you can see that the validation error message “The subject and the message can’t be the same.” is not related to any specific model property while the other error message (“A message with the same subject was previously saved in the DB.”) is associated to the Subject field. The errors that are associated to properties will be displayed in the view by @Html.ValidationMessageFor(model => model.[[FieldName]]). In order to display also the general errors that are related to the model state in general and not to a specific field we can use @Html.ValidationSummary(true). The true parameter inside the helper method indicates that we want to display in the validation summary only the error messages that are unrelated to properties. Now our view looks like this:

@model HelloWorld.Code.DataAccess.Message
@{
    ViewBag.Title = "Contact Us";
}
<h2>Contact Us</h2>
<hr/>
@Html.ValidationSummary(true)
@using (Html.BeginForm("ContactUs", "Home", FormMethod.Post))
{
    @Html.LabelFor(model => model.Name, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Name)
    @Html.ValidationMessageFor(model => model.Name)
    <br/>
    @Html.LabelFor(model => model.Email, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Email)
    @Html.ValidationMessageFor(model => model.Email)
    <br/>
    @Html.LabelFor(model => model.ContactReasonId, new { @class = "control-label" })
    @Html.DropDownListFor(model => model.ContactReasonId, Model.ContactReasonsList)
    @Html.ValidationMessageFor(model => model.ContactReasonId)
    <br/>
    @Html.LabelFor(model => model.Subject, new { @class = "control-label" })
    @Html.TextBoxFor(model => model.Subject)
    @Html.ValidationMessageFor(model => model.Subject)
    <br/>
    @Html.LabelFor(model => model.Message1, new { @class = "control-label" })
    @Html.TextAreaFor(model => model.Message1)
    @Html.ValidationMessageFor(model => model.Message1)
    <br/>          
    <input type="submit" name="Save" value="Save" />  
}
@if (ViewBag.SaveResult != null && ViewBag.SaveResult)
{
    <span style="color: green;">@ViewBag.SaveResultMessage</span>
}
@if (ViewBag.SaveResult != null && !ViewBag.SaveResult)
{
    <span style="color: red;">@ViewBag.SaveResultMessage</span>
}

And if we run the the site we can see the validation we added with IValidatableObject.
IValidatableObject result

6. Implementing client side validation

The MVC framework comes with some built-in client-validation support. Basically, the DataAnnotation attributes are able to support also client-side validation and, in this section, I will show the steps you need to do in order to enable it.

  • First make sure that the client-side validation is enabled in the web.config that is, the following two properties are set to true:
    <appSettings>
         <add key="ClientValidationEnabled" value="true"/>
         <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
    </appSettings>
    

    Notes:
    – This enables client-side validation for the entire site, if you want to enable it only for specific views, you can do this using code blocks inside the view and calling HtmlHelper.ClientValidationEnabled(true) and HtmlHelper.UnobtrusiveJavaScriptEnabled(true).
    – The MVC Framework supports unobtrusive client-side validation, meaning that the validation rules are defined using HTML data attributes instead of emitting client side JavaScript. Then, these attributes are processed with JavaScript in order to add the validation rules. The current MVC framework validation is based on the jQuery Validation library.

  • Then we have to add in our project the JavaScript files needed for validation. The easiest way is to use NuGet. Right click on References in Solution Explored and click on ‘Manage NuGet Packages…’. In the window that opens make sure that the Online section is selected and search for jQuery. Fron here you can install: jQuery, jQuery Validation and Microsoft jQuery Unobstructive Validation.
    Adding validation libraries using NuGet
  • Now we will include those files in the _Layout.cshtml file, just before the “scripts” section, so they will be available in all our pages.
    <script type="text/javascript" src="~/Scripts/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="~/Scripts/jquery.validate.js"></script>
    <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.js"></script>
    @RenderSection("scripts", required: false)
    

    Note: It would be better to include those files using Bundling, which is a new feature in asp.net MVC 4 that allows us to easily combine/bundle multiple JavaScript/CSS files into a single file so that we reduce the number of file requests which will improve the first page load performance. But, for keeping things simple we don’t use bundling for the moment.

  • If you run now the application you will notice that the validation we added with DataAnnotation attributes takes place on the client-side with no post back to the server.

7. Implementing remote validation

Sometimes we need to make some checks that require some server-side resources and can not be implemented only with client-side code. For example, in the context of user accounts, when registering a new account we might want to check if a username is not already taken. We can implement this with as a server-side validation but this will generate a round-trip to the server and it would be nicer to make the check via Ajax. The MVC framework provides support for remote validation with the help of the [Remote] attribute. Let’s use it to check if there is another message in the DB from the same e-mail address. We will add a remote attribute to the Email property:

[Remote("ValidateEmail", "Home")]

The first parameter specifies the method used for validation and the second parameter indicates the name of the controller that contains the validation method. So in our Home controller we will add:

public JsonResult ValidateEmail(string email)
{
	string message;
	return Message.EmailAlradyExists(email, out message)
			   ? Json(message, JsonRequestBehavior.AllowGet)
			   : Json(true, JsonRequestBehavior.AllowGet);
}

And now we add inside the model the method that actually connects to the DB to check if the e-mail was already used. Please remember that we shouldn’t place data access calls or business rule validation inside the controller.

public static bool EmailAlradyExists(string email, out string validationMessage)
{
	validationMessage = string.Empty;
	using (var context = new MvcDemoEntities())
	{
		var result = context.Messages.Any(m => string.Compare(m.Email, email, StringComparison.CurrentCultureIgnoreCase) == 0);
		if (result)
			validationMessage = "This e-mail address is already used";

		return result;
	}
}

So, when we use the [Remote(“ActionMethodName”,”ControllerName”)] attribute the MVC framework uses jQuery to make an asynchronous call to the server in order to check the validation rule defined in the action method. This explains why we are returning a JsonResult from the ActionMethodName. Now, if you run the site and enter an already used e-mail, when the e-mail text box looses the focus, the remote validation will be fired and you will see an error message like in the image below.
Remote validation

8. Summary

In this post we implemented data validation at the model level and we displayed the error messages in the user interface making use of the helper methods Html.ValidationMessageFor and Html.ValidationSummary.

We showed that even if it is possible to add validation rules in the controller it is not recommended to do so because the controller should contain only logic related to the application flow control hence we should always implement data validation in the model because it’s its role to define the domain data and the business logic to apply on that data.

We showed how DataAnnotation attributes can provide an easy way to validate individual properties on the model and, for more complex validation validation rules, we implemented the IValidatableObject interface.

Finally you can download the source code of this second MVC demo application from Codeplex.

2 comments on “Data validation

  1. Hi! I`ve found a lot of useful information in your blog, especially Forms Authentication in ASP.NET MVC 4 helped me a lot! Thanks! I like the way you write your posts – very clean and clear!
    I`ve tried to reproduce all ways the validation is done, downloaded the second project from CodePlex and faced such an error. Maybe you could help me.
    1) I`ve disabled client side validation.
    2) Tried to submit an empty form of ContactUs
    3) Got an exception

    System.Data.ConstraintException was unhandled by user code
    HResult=-2146232022
    Message=This property cannot be set to a null value.
    Source=System.Data.Entity
    StackTrace:
    at System.Data.EntityUtil.ThrowPropertyIsNotNullable(String propertyName)
    at System.Data.Objects.DataClasses.StructuralObject.SetValidValue(String value, Boolean isNullable)
    at HelloWorld.Code.DataAccess.Message.set_Name(String value) in f:\Documents\Visual Studio 2013\Projects\Demos\MvcDemoTwo\MvcDemoTwo\HelloWorld\HelloWorld\Code\DataAccess\MvcDemoDb.Designer.cs:line 318
    InnerException:

    Seems that we`re passing during HttpGet a Message entity to the form and when submitting an empty one Entity Framwok says that it`s not allowed those fields to be emty.
    We never get to “if(Modelstate.IsValid){}” so far.
    I`ve used CodeFirst before with the same validation approach and never got such exceptions. What could you recommend?
    Thanks!

    • Thank you for your kind words and please excuse me for replying after so much time, I’ve been a little busy lately :).

      The exception you got is normal/expected and I’m glad you asked about it because I know that other got confused too. First, in Visual Studio, try to launch the application without debugging (CTRL + F5). Press the save button without entering any value and you’ll see that you’ll get the expected result (the validators will be visible displaying in red the text ‘Required’) – there is no yellow screen of death :D. But, doing the same in debug mode, you’ll get the error you described.

      Now I’ll try to explain what happens:

      • when you press the button a POST request is sent to the server
      • the MVC framework will identify the controller action to be called, in this case HomeCotroller.ContactUs(Message message)
      • before invoking the action, the model binder will try to create a Message object and populate all its properties based on the form values received in the HTTP POST request.
      • In our case the model binder will create a Message object and will set all the properties of this object to null because all the input fields were empty when the page was submitted.
      • But the Message class (that, as you said, was generated by EF Database First) is designed to throw a System.Data.ConstraintException when a property that doesn’t allow null values is set to null.
      • After this exception is thrown the MVC framework will actually handle it (you saw that the application works with no YSOD 🙂 )
      • but Visual Studio breaks to that exception because the Message class was generated by the EF inside the solution, and, with the default settings, Visual Studio is set to break at any exception raised inside user code (that is any project included in the opened solution).

      So, there is no actual problem to fix but this is annoying. Of course you can choose to press F5 after this error occurs and go on but it get’s frustrating soon.

      To get rid of this I changed the settings in Visual Studio like this:
      – in the main menu I clicked Debug -> Exceptions…
      – in the window that opens I expanded “Common Language Runtime Exceptions”
      – and then I expand “System.Data” and I unchecked “System.Data.ConstraintException” like in the image below.
      Stop the debugger from breaking at that exception
      Hope it helps.

      Thanks again for writing this 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