Remote ASP.NET Model Validation with generic version of AddModelError

by Pawel Olesiejuk on 29th October 2012

Post image for Remote ASP.NET Model Validation with generic version of AddModelError

Validation is an extremely important part of a software system. It prevents users from inserting incorrect data. Although it seems to be a piece of cake, it’s not. It’s hard to keep validation logic separated from business logic. This problem, as almost all of known to developeres problems, has as many solutions as there are programmers. Nevertheless, ASP.MVC comes with a common solution to it. It’s called Model Validation. The main idea behind it is to put all validation in one place without mixing it with business logic. This post will show you how to use it in a proper manner and how to handle common issues and scenarios, such as remote validation. I’ll try to show some of the best practices that are ready to use in production.

It’s easy to use, it’s extendable, it’s perfect!

No it’s not. Nothing’s perfect. Oh, maybe Nature is but this is another subject. Anyway, it’s a damn good feature and it goes with unobtrusive jQuery validation by default (all you need to do in order to enable it is to include javascript file). No extra code needed. Now, back to the point. All we need to do, in order to profit out of Data Validation functionality, is to add an attribute to a certain property in our (View)Model. E.g.

public class Employee
{
    [Required]
    [Length(3,64)]
    public string Name { get; set; }

    [Required]
    [Email]
    public string Email{ get; set; }
    [Range(0.01,100000)]

    [Required]
    public decimal  { get; set; }
}

Some of those attributes are created by MVC team, some of them are or can be custom. If you want to know more about Model Validation I recommend this article. Model Validation is based on ModelStateDictionary, to which we have access from a controller via ModelState property. It’s recommended to do the checking by hand on this object, as follows:

[HttpPost]
public ActionResult Save(Employee model){
	if (!ModelState.IsValid){
		return View();
	}
	...
	return RedirectToAction("Overview");
}

Why should we do to this? First of all, if you don’t use unobtrusive validation it’s just necessary. It’s the only way to show validation messages to the user. But even if we DO use unobtrusive validation, and it would seem that the user cannot insert invalid values, it’s still not true. The user can always turn javascript off or send request using some other software (or browser plugin). Thus, we’re obliged to do the checking also on server’s side.

Remote validation

Ok, I think we’re in the right place to begin the main subject: additional validation on server’s side. Suppose that we want to check whether Email property is unique in our database. There are two ways of doing so. The first one is to add Remote validation (via ajax). The second consists in adding validation on server’s side (on controller’s action). Actually, the best option is to use both because of the same reason that was mentioned before. To add remote validation we need to add Remote attribute with action and controller names.

[Remote("ValidateEmail", "Home")]
public string Email{ get; set; }

The second step is to write action to validate uniqueness.

public ActionResult ValidateEmail(string name)
{
	if (IsEmailUnique(name))
		return Json(true, JsonRequestBehavior.AllowGet);
	return Json(EmailUniquenessMessage, 
				JsonRequestBehavior.AllowGet);
}

The method used for remote validation should return Json result. The first parameter should be either boolean value or string. If boolean value is true it means that validation succeeded, however if false, then default validation message is shown. In case we want to use custom validation message, we should pass it as first parameter (and once we do so, it means that validation fails).

For the sake of an example, let’s ignore body of IsEmailUnique method. If we have unobtrusive validation on, we should be able to see that when we insert incorrect data, validation fails and nothing happens. However if we try turning javascript off in our browser and send invalid data or send a post using some web tool, we can see that we’ve cheated validation. What we should do now is validate data on the server’s side. It’s a little more complicated than with Data Validation, but it’s still pretty straightforward.

[HttpPost]
public ActionResult Save(Employee model)
{
	if (!IsEmailUnique(model.Name))
		ModelState.AddModelError("Email", 
			EmailUniquenessMessage);
	if (!ModelState.IsValid)
	{
		return View(model);
	}
	return RedirectToAction("Success", model);
}

Get rid of magic string

Everything works as it should, so we are happy. Aren’t we? I’m a great opponent of “magic strings” in code. And here we add a new one, so I’m not as happy as I’d like to be. “Email” is a name of property on our Employee model. Unfortunately there is no generic method (‘Why’, you asked? I don’t know. I even wrote an issue on asp.net community site. If you have an idea why, please, write a comment below this article). Worry not, for we can write it ourselves!

public static void AddModelError<TModel>(
	this ModelStateDictionary state,
	Expression<Func<TModel, string>> expression,
	string message)
{
	var fieldName = ExpressionHelper
						.GetExpressionText(expression);
	state.AddModelError(fieldName, message);
}

This extension does the job. Let’s see how it works.

[HttpPost]
public ActionResult Index(Employee model)
{
	if (IsEmailUnique(model.Name))
		ModelState.AddModelError<Employee>(x => x.Email, 
					EmailUniquenessMessage);
	if (!ModelState.IsValid)
	{
		return View(model);
	}
	return RedirectToAction("Success", model);
}

Now, that’s a sight for sore eyes ;-) ! Do you have any suggestions about this solution? Or maybe you have implemented this solution yourself before? If so, please share your thoughts below.

  • http://www.facebook.com/maciej.panecki Maciej Panecki

    One note thou, when remote validation is pending, by default jQuery validation sets “isValid” state for that field to “true”, so form submitting will not be postponed and it will/can be submitted before ajax call will return proper validation value.

  • http://blog.goyello.com Paweł Olesiejuk

     True, but we are protected from it by server side validation on controller action.

  • kaili

    Thanks for the postwow gold, I experienced no idea about this, can’t wait around to try it out.

    great work! I like it very diablo 3 gold for salemuch!