Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Core 2.0 MVC Model Validation

0.00/5 (No votes)
1 Sep 2017 1  
How to validate input models in ASP.NET Core MVC. Continue reading...

Problem

How to validate input models in ASP.NET Core MVC.

Solution

We discussed model binding in a previous post, using that sample as a template, add Save() method in HomeController:

[HttpPost]
        public IActionResult Save(EmployeeInputModel model)
        {
            if (ModelState.IsValid)
                return Ok(model);

            return BadRequest(ModelState);
        }

Add a model annotated with various validation attributes:

public class EmployeeInputModel
    {
        public int Id { get; set; }

        [Required]
        public string EmployeeNo { get; set; }

        [StringLength(10)]
        [MinLength(3)]
        public string Surname { get; set; }

        [EmailAddress]
        public string Email { get; set; }

        [Url]
        public string BlogUrl { get; set; }

        [DataType(DataType.Date)]
        [Display(Name = "Date of Birth")]
        public DateTime BirthDate { get; set; }

        public Gender Gender { get; set; }

        [Range(0, 10000.00)]
        public decimal Salary { get; set; }

        public bool IsPartTime { get; set; }

        public AddressInputModel Address { get; set; }
    }

    public class AddressInputModel
    {
        [Required]
        [RegularExpression("[A-Za-z0-9].*")]
        public string Line { get; set; }
    }

Discussion

Data coming from web (forms, HTTP request, etc.) must be validated. No exceptions! Model Validation is the mechanism ASP.NET Core MVC provides for this purpose. Validation triggers after the Model Binding but before the action executes.

Validations can be added by using Validation Attributes. Framework provides a lot of useful validation attributes and we can even create our own too.

Model State

ControllerBase provides a property ModelState to work with model validation. MVC will populate this dictionary with validation errors after model binding. You could then verify its IsValid property in the action method to act accordingly.

ModelState has a method to add errors to the model at server side (e.g. in Controller). This is used for validations that require database access and report errors back to the client. Modified Save() method demonstrates this:

[HttpPost]
        public IActionResult Save(EmployeeInputModel model)
        {
            // simulate DB call to check existence of Id
            if (model.Id == 1)
                ModelState.AddModelError("Id", "Id already exist");

            if (ModelState.IsValid)
                return Ok(model);

            return BadRequest(ModelState);
        }

ModelState:

model state

Validation Attributes

Framework provided validation attributes exist in System.ComponentModel.DataAnnotations. In the sample application, I’ve demonstrated the use of commonly used attributes.

An interesting aspect of model validation is that even without any attributes, MVC will do some basic validations based on data types. Below is the result of a request when model has no validation and no data was supplied, note that empty values fail for Integer, DateTime and Boolean properties:

error

Custom Validation

Custom validation attributes are useful to create reusable and application specific validation logic. There are two ways to implement this (both can be used on the same model):

Inherit ValidationAttribute and Override its IsValid Method

IsValid takes in a parameter of type ValidationContext, which has few useful properties:

  • ObjectInstance: Returns the model on which the attribute is applied. Casting this to a specific model class gives you access to its properties.
  • DisplayName: Returns either the name of model’s property or string specific in [Display(Name = “…”)] attribute applied to the property.

ValidationAttribute class also provides few useful properties and methods:

  • ErrorMessage: Returns the error message set on the attribute when declaring it in the model.
  • FormatErrorMessage: Returns the generic message, i.e., the field {0} is invalid, where {0} is replaced with DisplayName.

IsValid method returns ValidationResult class, either through its static field Success or by initializing a new instance (in case of an error).

Below is a custom validation attribute applied on the BirthDate property of our model:

[DataType(DataType.Date)]
        [AgeCheck]
        [Display(Name = "Date of Birth")]
        public DateTime BirthDate { get; set; }

    public class AgeCheck : ValidationAttribute
    {
        protected override ValidationResult IsValid(
object value, ValidationContext validationContext)
        {
            var model = validationContext.ObjectInstance as EmployeeInputModel;

            if (model == null)
                throw new ArgumentException("Attribute not applied on Employee");

            if (model.BirthDate > DateTime.Now.Date)
                return new ValidationResult(GetErrorMessage(validationContext));

            return ValidationResult.Success;
        }

        private string GetErrorMessage(ValidationContext validationContext)
        {
            // Message that was supplied
            if (!string.IsNullOrEmpty(this.ErrorMessage))
                return this.ErrorMessage;

            // Use generic message: i.e. The field {0} is invalid
            //return this.FormatErrorMessage(validationContext.DisplayName);

            // Custom message
            return $"{validationContext.DisplayName} can't be in future";
        }
    }

Implement IValidatableObject in Your Model

To implement more complex validations that need access to more than one property in your model, IValidatableObject works best, by implementing its Validate() method. A key thing to remember is that these validations will run after attribute based validations are successful.

Validate() method returns IEnumerable, because this method can validate more than one property of the model providing a validation error for each.

Here is a sample for our example model:

public class EmployeeInputModel : IValidatableObject
    {
        ...

        public IEnumerable<ValidationResult> Validate(
            ValidationContext validationContext)
        {
            if (!string.IsNullOrEmpty(BlogUrl) &&
                    BlogUrl.Contains("tahirnaushad.com"))
                yield return new ValidationResult(
                    "URL already taken", new[] { "BlogUrl" });
        }
    }

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here