The community is working on translating this tutorial into Persian, but it seems that no one has started the translation process for this article yet. If you can help us, then please click "More info".
Custom Model Validation
If you ever feel that the built-in validation methods, as discussed in the previous articles, are not enough, ASP.NET MVC offers you the possibility of implementing your own validation logic. There may be many occasions where this is a good idea, but you should of course first make sure that there isn't already an easier, built-in alternative, like the [RegularExpression] option, which offers great flexibility.
With that said, it's really not that difficult to implement your own validation logic. There are a couple of ways to do it, so let's examine both. We'll expand on our previous examples in the last couple of articles, where we introduced the WebUser class. We'll add a Birthday property to it, and then do some custom validation of it.
Custom validation with ValidationAttribute
If you want validation similar to the built-in kind, where you can add DataAnnotations to your properties (e.g. [Required] or [EmailAddress]), you can simply create a class and let it inherit from the ValidationAttribute. After that, simply override the IsValid() method and write your own logic.
For our example, I have added some pretty basic (and perhaps slightly silly) validation to the IsValid() method, just to show you the possibilities. Some of it could have been handled with [Range] validation, but not all of it, and that's the beauty of custom validation - you can add as much logic as you need to. Also, you can return individual error messages for each of your checks, like I do in my example:
public class WebUserBirthdayValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
DateTime birthday = (DateTime)value;
if(birthday.Year < 1900)
return new ValidationResult("Surely you are not THAT old?");
if(birthday.Year > 2000)
return new ValidationResult("Sorry, you're too young for this website!");
if(birthday.Month == 12)
return new ValidationResult("Sorry, we don't accept anyone born in December!");
return ValidationResult.Success;
}
}
As you can see, we perform multiple checks and return a new instance of the ValidationResult class if they match. If none of them is true, we can assume that we have a valid value, so we return the ValidationResult.Success result. Applying this validation logic to the Birthday property of our WebUser class is as simple as using one of the built-in validation mechanisms:
public class WebUser
{
// Other properties here...
[WebUserBirthdayValidationAttribute]
public DateTime Birthday { get; set; }
}
If you want to test it, you just need a Controller to handle the GET and POST request, as well as a View with a FORM to edit the property, like this:
public class ValidationController : Controller
{
[HttpGet]
public IActionResult CustomValidation()
{
return View();
}
[HttpPost]
public IActionResult CustomValidation(WebUser webUser)
{
if(ModelState.IsValid)
return Content("Thank you!");
else
return View(webUser);
}
}
@model HelloMVCWorld.Models.WebUser
@using(var form = Html.BeginForm())
{
<div>
@Html.LabelFor(m => m.Birthday)
@Html.TextBoxFor(m => m.Birthday)
@Html.ValidationMessageFor(m => m.Birthday)
</div>
<input type="submit" value="Submit" />
}
Congratulations, you have now implemented custom validation logic for one of your properties.
One of the really cool benefits of this approach is that your validation logic isn't necessarily tied to a specific Model - instead of calling your class WebUserBirthdayValidationAttribute, you could have just called it BirthdayValidationAttribute and re-used it across all types of birthday-related properties in your project. On the other hand, you will run into situations where it makes perfect sense to tie your validation logic to your Model - we'll see how that works in the next section of this article.
Custom validation with IValidatableObject
As an alternative to the approach we just saw, where we implemented a custom-made version of the ValidationAttribute, we can make our Model implement the IValidatableObject interface. By doing so, we can contain the logic needed for custom validation directly on the Model (class), instead of implementing a new class. This makes even more sense if the validation logic you're looking to add concerns more than one property, since the validation is now no longer tied to a single, specific property.
The IValidatableObject interface only has a single method: Validate(). It should contain all the logic you need that can't be applied to your properties through the built-in mechanisms, so in other words, you are still free to combine both regular and custom validation. We have done just that in this new class called WebUserValidatable (but please notice that the class name doesn't have to include the word "Validatable" or anything like that - you are still free to call it whatever you want):
public class WebUserValidatable : IValidatableObject
{
[Required(AllowEmptyStrings = true, ErrorMessage = "You must enter a value for the First Name field!")]
[StringLength(25, ErrorMessage = "The First Name must be no longer than 25 characters!")]
public string FirstName { get; set; }
[Required(ErrorMessage = "You must enter a value for the Last Name field!")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "The Last Name must be between 3 and 50 characters long!")]
public string LastName { get; set; }
public DateTime Birthday { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(this.Birthday.Year < 1900)
yield return new ValidationResult("Surely you are not THAT old?", new[] { "Birthday" });
if(this.Birthday.Year > 2000)
yield return new ValidationResult("Sorry, you're too young for this website!", new[] { "Birthday" });
if(this.Birthday.Month == 12)
yield return new ValidationResult("Sorry, we don't accept anyone born in December!", new[] { "Birthday" });
}
}
You will notice two things: The logic I'm using is basically the exact same as in our example with the ValidationAttribute, but there are also a couple of differences. First of all, we don't have to return anything if the validation succeeds. Second of all, we now include the name of the property (or properties, if there are multiple) relevant to the error message, in this case Birthday. The reason is quite simple: When the validation is no longer tied to a specific property, through DataAnnotations, the framework can't know which property the error should relate to, so we provide that information. This allows us to do the type of validation that involves multiple properties, like this:
if((this.Birthday.Month == 12) && (this.FirstName != "Santa"))
yield return new ValidationResult("Sorry, to be born in December, we require that your first name is Santa!", new[] { "Birthday", "FirstName" });
The example is completely silly, but a nice demonstration of how easily you can make one or several of your validation checks relate to multiple properties. If you have individual error messages in your markup for each of the fields, the generated error message will now be attached to both fields.
If you want to see this in action, simply add Controller logic and a suiting View with a FORM to your project:
public class ValidationController : Controller
{
[HttpGet]
public IActionResult CustomValidation()
{
return View();
}
[HttpPost]
public IActionResult CustomValidation(WebUserValidatable webUser)
{
if(ModelState.IsValid)
return Content("Thank you!");
else
return View(webUser);
}
}
@model HelloMVCWorld.Models.WebUserValidatable
@using(var form = Html.BeginForm())
{
<div>
@Html.LabelFor(m => m.FirstName)
@Html.TextBoxFor(m => m.FirstName)
@Html.ValidationMessageFor(m => m.FirstName)
</div>
<div>
@Html.LabelFor(m => m.LastName)
@Html.TextBoxFor(m => m.LastName)
@Html.ValidationMessageFor(m => m.LastName)
</div>
<div>
@Html.LabelFor(m => m.Birthday)
@Html.TextBoxFor(m => m.Birthday)
@Html.ValidationMessageFor(m => m.Birthday)
</div>
<input type="submit" value="Submit" />
}
Summary
Implementing your own, custom validation logic in your ASP.NET MVC project is very easy. There are two methods, one that ties directly to a specific property and one that ties directly to the Model, allowing you to validate multiple properties at the same time.