Model Validation in ASP.NET Core from Beginning to Expert

Model Validation in ASP.NET Core from Beginning to Expert

Model Validation is a process to ensure the data received from the View is appropriate to bind the Model. If it is not, then appropriate messages are displayed on the View that will help user to rectify the problem.

Let us understand it by applying Model Validation to a Job Application form. In this form there are different types of controls used for entering:

1. User’s name in an Input control.
2. User’s date of birth in an Input control.
3. User’s sex in a radio button.
4. User’s experience in select control.
5. Accept terms in a checkbox.

Understanding the Need for Model Validation

So create ‘JobApplication’ class inside the Models folder with code as shown below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ModelBindingValidation.Models
{
    public class JobApplication
    {
        public string Name { get; set; }

        public DateTime DOB { get; set; }

        public string Sex { get; set; }

        public int Experience { get; set; }

        public bool TermsAccepted { get; set; }
    }
}

The ‘JobApplication’ model class defines various properties on which model validation will be applied.

Next add 2 views, first the Index View inside the ‘Views/Job’ folder with code as given below:

@model JobApplication
@{
    Layout = "_Layout";
    ViewData["Title"] = "Job Application";
}

<h2>Job Application</h2>

<form class="m-1 p-1" method="post">
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="DOB"></label>
        <input asp-for="DOB" type="text" asp-format="{0:d}" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Sex"></label>
        <div>
            <input asp-for="Sex" type="radio" value="M" />Male
            <input asp-for="Sex" type="radio" value="F" />Female
        </div>
    </div>
    <div class="form-group">
        <label asp-for="Experience"></label>
        <select asp-for="Experience" class="form-control">
            <option value="Select">Select</option>
            <option value="0">Fresher</option>
            <option value="1">0-1 years</option>
            <option value="2">1-2 years</option>
            <option value="3">2-3 years</option>
            <option value="4">3-4 years</option>
            <option value="5">4-5 years</option>
        </select>
    </div>
    <div class="form-group">
        <input asp-for="TermsAccepted" />
        <label asp-for="TermsAccepted" class="form-check-label">
            I accept the terms & conditions
        </label>
    </div>
    <button type="submit" class="btn btn-primary">Submit Application</button>
</form>

It contains a form with model binding applied to its fields. The user will have to fill and submit the form.

Also add ‘Accepted’ view inside the ‘Views/Job’ folder with code given below:

@model JobApplication
@{
    Layout = "_Layout";
    ViewData["Title"] = "Accepted";
}

<h2>Accepted</h2>

<table class="table table-bordered">
    <tr>
        <th>Your name is:</th>
        <td>@Model.Name</td>
    </tr>
    <tr>
        <th>Your date of birth is:</th>
        <td>@Model.DOB.ToString("d")</td>
    </tr>
    <tr>
        <th>Your sex is:</th>
        <td>@Model.Sex</td>
    </tr>
    <tr>
        <th>Your experience is:</th>
        <td>@Model.Experience</td>
    </tr>
    <tr>
        <th>Have you accepted terms:</th>
        <td>@Model.TermsAccepted</td>
    </tr>
</table>
<a class="btn btn-success" asp-action="Index">Back</a>

This view will show the submitted values by the user.

You will also have to create a controller file and name it ‘Job’. This controller will contains 2 action methods –

1. HttpGet version of Index – that will shown the Index View for the user to fill the form.
2. HttpPost version of Index – that will be invoked once the user submits the form. This action gets all the values filled by the user, in its parameter of type JobApplication, through Model Binding. It then invokes the ‘Accepted’ View and passes these values as its model.

The code for these action methods are given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ModelBindingValidation.Models;

namespace ModelBindingValidation.Controllers
{
    public class JobController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Index(JobApplication jobApplication)
        {
            return View("Accepted", jobApplication);
        }
    }
}

Now run your application and go to the URL – ‘/Home/Job’, where you will find the Job Application form.
The form looks normal but there is no Model Validation applied to it.

Fill the values on the controls as given below:

1. Name – Donald
2. DOB – 24-12-1986
3. Sex – leave it untouched
4. Experience – leave it untouched
5. I accept the terms & conditions – leave it untouched

The image below which shows these filled values:

job application

When you click the Submit Application button you will see your filled values, as shown in the image below:

job application filled values

But Wait! There is some flaw. You filled ‘24-12-1986’ for DOB but it is showing ’01-01-0001’.

This happens because the DOB field has to be entered in MM-DD-YYYY format buy you entered it in DD-MM-YYYY format, and so this value is not binded to the DOB property of the JobApplicaiton class which instead showed you the default value of a DataTime (01-01-0001).

The form also has no way to force the user to choose his/her sex, experience and Terms-Conditions.

To prevent user from filling wrong date for DOB control and force him/her to enter all his information in the controls, we use the process of Model Validation.

Model Validation from AddModelError() method & ModelState.IsValid property

For doing Model Validation you have to know 2 things:
1. AddModelError()
2. ModelState.IsValid

AddModelError() method

The AddModelError() method is used to record a Model Validation error for a specified property.

The syntax of AddModelError() method is:

AddModelError(property, message);

property – it is the property of the model class.
message – a string that specifies the error message.

ModelState.IsValid property

The ModelState.IsValid is a property that returns true if all the model properties are valid and returns false otherwise.

Now I will use both of these two methods and property for doing Model Validation. So change the HttpPost version of Index action like as shown below:

[HttpPost]
public IActionResult Index(JobApplication jobApplication)
{
    if (string.IsNullOrEmpty(jobApplication.Name))
        ModelState.AddModelError(nameof(jobApplication.Name), "Please enter your name");

    if (jobApplication.DOB == Convert.ToDateTime("01-01-0001 00:00:00"))
        ModelState.AddModelError(nameof(jobApplication.DOB), "Please enter your Date of Birth");
    else if (jobApplication.DOB > DateTime.Now)
        ModelState.AddModelError(nameof(jobApplication.DOB), "Date of Birth cannot be in the future");
    else if (jobApplication.DOB < new DateTime(1980, 1, 1))
        ModelState.AddModelError(nameof(jobApplication.DOB), "Date of Birth should not be before 1980");

    if (string.IsNullOrEmpty(jobApplication.Sex))
        ModelState.AddModelError(nameof(jobApplication.Sex), "Please select your sex");

    if (jobApplication.Experience.ToString() == "Select")
        ModelState.AddModelError(nameof(jobApplication.Experience), "Please select your experience");

    if (!jobApplication.TermsAccepted)
        ModelState.AddModelError(nameof(jobApplication.TermsAccepted), "You must accept the Terms");

    if (ModelState.IsValid)
        return View("Accepted", jobApplication);
    else
        return View();
}

I have applied checks on every property of the ‘JobApplication’ class to ensure that the user provides some value for every property.

Name property

For the Name property the code – if (string.IsNullOrEmpty(jobApplication.Name)) checks if the user has not filled his/her name in the name control. In that case the code – ModelState.AddModelError(nameof(jobApplication.Name), “Please enter your name”); Model Validation error is recorded for the ‘Name’ property.

DOB property

There are 3 checks done for the DOB property to determine the problems, these are:

1. If user has not filled his/her DOB or provided some invalid date like “apple” for this field. It is detected by the code if (jobApplication.DOB == Convert.ToDateTime(“01-01-0001 00:00:00”)).

The DateTime property has the default value of 01-01-0001 00:00:00 which happens when user has not supplied any value.

2. If user has provided a future date (from the current datetime). It is checked by else if (jobApplication.DOB > DateTime.Now).

3. If the user has provided DOB before 1980. It is checked by the code else if (jobApplication.DOB < new DateTime(1980, 1, 1)).

Appropriate messages are provided whenever the checks detect some problem.

Sex property

Two radio buttons are provided for selecting the sex of the user. If the user does not select his/her sex, which is found out by the code if (string.IsNullOrEmpty(jobApplication.Sex)), in such a case error message is recorded for the Sex property by the code line – ModelState.AddModelError(nameof(jobApplication.Sex), “Please select your experience”);.

Experience property

A select control is given for selecting the Experience. The default selected value is ‘Select’, so the code if (jobApplication.Experience.ToString() == “Select”) determines if the user has not selected his/her experience. In such a case an error message is recorded for the Experience property.

TermsAccepted property

If the user has not selected the checkbox control for the TermsAccepted property then error message is recorded. The code if (!jobApplication.TermsAccepted) finds out is the checkbox is unchecked.
After I have validated all the properties I check ModelState.IsValid to see if there are no errors.

It will return true if there are no errors, in that case I am returning to the ‘Accepted’ View and passing the ‘jobApplication’ model to it.

It ModelState.IsValid returns false then it means there are some model errors so I am simply returning to the same view (Index View).

The below code does this checking for the ModelState:

if (ModelState.IsValid)
    return View("Accepted", jobApplication);
else
    return View();

You can now check how this works by filling the form in the URL – ‘/Home/Job’. You will notice that you are now required to fill and select all the values, also the date value should be not in future and must not be before the year 1980.

Check the HTML formed of the Controls

Model Validation feature adds the CSS Class input-validation-error to all the controls that have failed the validation checks i.e. whenever some error message is recorded for a property by the method – ModelState.AddModelError().

To check it, open the URL – ‘/Home/Job’ and click the ‘Submit Application’ button without filling any value. Then check the HTML formed for the ‘Name’ control, which will contain be class input-validation-error as shown below:

<input class="form-control input-validation-error" type="text" id="Name" name="Name" value="">

You will also find the ‘input-validation-error’ class on other controls.

I can use this to add red border to controls that have errors by adding some CSS code. So add a style block on the bottom of the View like shown below:

@model JobApplication
@{
    Layout = "_Layout";
    ViewData["Title"] = "Job Application";
}

<h2>Job Application</h2>

<form class="m-1 p-1" method="post">
...
</form>

<style>
    .input-validation-error {
        border-color: red;
    }
</style>

Now you will see the controls having errors highlighted by red border as shown in the image below:

highlight controls having errors

Displaying Model Validation Errors in asp-validation-summary Tag Helper

The asp-validation-summary Tag Helper is used to show the validation errors that have been added in the Action method. Add this Tag Helper to a new div in the View and you will see all the recorded validation errors, see the below code:

@model JobApplication
@{
    Layout = "_Layout";
    ViewData["Title"] = "Job Application";
}

<h2>Job Application</h2>

<div asp-validation-summary="All" class="text-danger"></div>
<form class="m-1 p-1" method="post">
    ...
</form>

<style>
    .input-validation-error {
        border-color: red;
    }
</style>

The ‘asp-validation-summary’ Tag Helper is applied on the div so the validation errors will be shown on the div element. Note that the text-danger is a Bootstrap class that gives red color to text.
Now rerun your application and submit the form on the URL ‘/Home/Job’, without filling any value in any control.
You will see all the Model Validation messages displayed on your browser, as shown by the image below:

model validation messages using asp validation summary

You can give 3 values to the ‘asp-validation-summary’ Tag Helper, these are given below:

Name Description
All Displays all the recorded validation messages.
ModelOnly Displays only the validation message that are recorded for the entire model. It excludes those validation messages that are recorded for individual properties.
None Does not displays any validation messages./td>

Displaying Property Model Validation Messages using the asp-validation-for Tag Helper

Obviously it is more useful to display the Model Validation Error Messages alongside the controls that contain the problematic data. This you can achieve by using the asp-validation-for tag helper.

For example to show Model validation error messages for the name property, add this tag helper to a new span element like:

<span asp-validation-for="Name"></span>

Now in the Index View of the Job Controller, add a span control containing the asp-validation-for tag helper, for each control to show its validation errors, as listed in the below code:

@model JobApplication
@{
    Layout = "_Layout";
    ViewData["Title"] = "Job Application";
}

<h2>Job Application</h2>

<div asp-validation-summary="All" class="text-danger"></div>
<form class="m-1 p-1" method="post">
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="DOB"></label>
        <input asp-for="DOB" type="text" asp-format="{0:d}" class="form-control" />
        <span asp-validation-for="DOB" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Sex"></label>
        <div>
            <input asp-for="Sex" type="radio" value="M" />Male
            <input asp-for="Sex" type="radio" value="F" />Female
        </div>
        <span asp-validation-for="Sex" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Experience"></label>
        <select asp-for="Experience" class="form-control">
            <option value="Select">Select</option>
            <option value="0">Fresher</option>
            <option value="1">0-1 years</option>
            <option value="2">1-2 years</option>
            <option value="3">2-3 years</option>
            <option value="4">3-4 years</option>
            <option value="5">4-5 years</option>
        </select>
        <span asp-validation-for="Experience" class="text-danger"></span>
    </div>
    <div class="form-group">
        <input asp-for="TermsAccepted" />
        <label asp-for="TermsAccepted" class="form-check-label">
            I accept the terms & conditions
        </label>
        <span asp-validation-for="TermsAccepted" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Submit Application</button>
</form>

<style>
    .input-validation-error {
        border-color: red;
    }
</style>

Run your application once more and submit the form without filling any value. This time you will see the individual error messages displayed besides each control.

The below image shows these error messages:

model validation errors besides each control

Model-Level Error Messages

Model-Level Error Messages are those that are applicable to the model as a whole and not to individual properties. You can apply then providing “” to the property parameter of the AddModelError() method:

ModelState.AddModelError("", "Some Model-Level error");  

Suppose I do not want a person named ‘Osama Bin Laden’ to apply for the Job so add a new else if block to check whether the name is ‘Osama Bin Laden’ and if it is so then provide a Model-Level validation error message. This is shown in the code below:

[HttpPost]
public IActionResult Index(JobApplication jobApplication)
{
    if (string.IsNullOrEmpty(jobApplication.Name))
        ModelState.AddModelError(nameof(jobApplication.Name), "Please enter your name");
    else if(jobApplication.Name=="Osama Bin Laden")
        ModelState.AddModelError("", "You cannot apply for the Job");
    
    // removed for clarity
}

Finally go to the Index View and change the asp-validation-summary to ModelOnly:

...

<h2>Job Application</h2>

<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form class="m-1 p-1" method="post">
    ...
</form>

<style>
    .input-validation-error {
        border-color: red;
    }
</style>

Now go to the form and fill ‘Osama Bin Laden’ on the name control and click the button. This time you will only see the Model-Level error message (You cannot apply for the Job) on the div.

See the image below:

model level error message

Model Validation from Data Annotations

There is another way to do Model Validations is by the use of Validation Attributes in the Model class. This process is known as Model Validation from Data Annotation. This process is easy, fast and removed code duplication.

The Validation Attributes are described in the table below:

Name Description
[Required(ErrorMessage = “Some Message”)] Ensures that the value is not empty. Can be applied to values who have a default value of null type like int?, float?, string.
[StringLength(max,min)] Ensures that the string’s length is from min to max values, min & max are included. Example [StringLeght(2,5)] allows all string from length 2 to 5.
[Compare(“OtherProperty”)] Ensures that the property to which it is applied and the property it specifies (i.e.OtherProperty) have the same value.
[Range(min,max)] Ensures that the numeric value lies from the min to max values, min and max values are included. Example [Range(2,5)] allows values from 2,3,4 and 5.
[RegularExpression(“pattern”)] Ensures that the value matches the regular expression pattern. Example for allowing proper email addresses.

Now go to the ‘JobApplication’ Model class and import the namespace System.ComponentModel.DataAnnotations, and then add the attributes to the properties like shown in the code below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;

namespace ModelBindingValidation.Models
{
    public class JobApplication
    {
        [Required]
        [Display(Name = "Job applicant name")]
        public string Name { get; set; }

        public DateTime DOB { get; set; }

        [Required(ErrorMessage = "Please select your sex")]
        public string Sex { get; set; }

        [Range(0, 5)]
        public int Experience { get; set; }

        [Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the Terms")]
        public bool TermsAccepted { get; set; }
    }
}

In the Name field I applied 2 attributes:

1. Display
2. Required

[Required]
[Display(Name = "Job applicant name")]
public string Name { get; set; }

With the [Display(Name = “Job applicant name”)] attribute I have changed the display name of the property in the View to ‘Job applicant name’, and since the [Required] attribute has no ErrorMessage specified, therefore the ‘Name’ property value (which is ‘Job applicant name’) of the ‘Display’ attribute is automatically taken as the ‘ErrorMessage’ value for the ‘Required’ attribute.

There is no need to apply any validation attribute for ‘DOB’ field which is of type DateTime. This is because the DateTime field has a default value of 01-01-0001 00:00:00 so when user does not fill any value to this field then 01-01-0001 00:00:00 will be used, thus making the Required attribute useless.

In such a case and Error message ‘The value ” is invalid.’ will be shown.

However if you still want to apply [Required] to work on this field then make it a nullable type like:

[Required(ErrorMessage = "Please enter your Date of Birth")]
public DateTime? DOB { get; set; }

/I have applied the [Required(ErrorMessage = “Please enter your Date of Birth”)] attribute so it ensures that the values for the DOB field cannot be empty and should be in proper date format.

In the same way the Required attribute is applied for the Sex field.

For the ‘Experience’ field only the Range attribute is applied. This is because it is sufficient to do the validation for my select control as the select values contains experience from 0 to 5 only.

[Range(0, 5)]
public int Experience { get; set; }

Finally on the ‘TermsAccepted’ field I have applied the Range attribute like:

[Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the Terms")]
public bool TermsAccepted { get; set; }

I cannot apply the Required attribute for forcing user to check the checkbox because since this field is of type bool so a default value of false will be sent if the checkbox is not checked, and thus making the Required attribute useless.

To work around I have used the Range attribute and provided a type of bool. I have also set both the min and max values as true and thus made the user to check the checkbox.

Now finally you have to remove your validation codes from the action method. So change your ‘Index’ Action of the Job Controller code to:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ModelBindingValidation.Models;

namespace ModelBindingValidation.Controllers
{
    public class JobController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Index(JobApplication jobApplication)
        {
            if (ModelState.IsValid)
                return View("Accepted", jobApplication);
            else
                return View();
        }
    }
}

You can see I am able to remove a full load of validation code from the action method itself. In the action method I am only checking for the validity of the ModelState. The validation will show as usual, which you can see by clicking the ‘Submit Application’ button without filling anything on the form.

The image below shows it:

model validation from data annotations

You may ask what happened for validation of ‘Osama Bin Laden’, future date, and DOB only from 1980. The validation for these types cannot be done from the Data Annotations, you will have to do it in the Action method or by making a Custom Model Validation Attribute.

The below code includes these validations in the action method:

[HttpPost]
public IActionResult Index(JobApplication jobApplication)
{
    if (jobApplication.Name == "Osama Bin Laden")
        ModelState.AddModelError(nameof(jobApplication.Name), "You cannot apply for the Job");

    if (jobApplication.DOB > DateTime.Now)
        ModelState.AddModelError(nameof(jobApplication.DOB), "Date of Birth cannot be in the future");
    else if (jobApplication.DOB < new DateTime(1980, 1, 1))
        ModelState.AddModelError(nameof(jobApplication.DOB), "Date of Birth should not be before 1980");

    if (ModelState.IsValid)
        return View("Accepted", jobApplication);
    else
        return View();
}

Validating Email Address Format

The Data Annotations approach for doing validation makes adding new fields and there validation very simple. Suppose I want to include email field and ensure the email address is in correct format. For this I will use the [RegularExpression] attribute by providing the pattern for valid email as – ^[a-zA-Z0-9_\\.-][email protected]([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$.

So go to the ‘JobApplication’ class and add the new email field:

[RegularExpression("^[a-zA-Z0-9_\\.-][email protected]([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "E-mail is not valid")]
public string Email { get; set; }

Then go to the view and add:

<div class="form-group">
    <label asp-for="Email"></label>
    <input asp-for="Email" type="text" class="form-control" />
    <span asp-validation-for="Email" class="text-danger"></span>
</div>

And that’s all, you have now added new email field with proper validation. The below image shows the error message when email is not in proper format:

email validation data annotations

Custom Model Validation Attribute

You can also create your own Custom Model Validation Attribute that does the validation of values in the manner defined by you.

For this you have to create a class and inherit it from 2 classes:

1. Attribute
2. IModelValidator

The IModelValidator interface defines Validate() method which you have to implement in your class. This method is where you perform validation.

The skeleton of the Validate method is given below:

public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context) 
{
    ...
}

Inside this method you receive information about the property, that is to be validated, through an instance of the ModelValidationContext class.

The ModelValidationContext Class properties are described in the table below:

Name Description
Model Returns the property value that is to be validated.
Container Returns the object that contains the property.
ActionContext It provides the context data./td>

So create a new folder called ‘Infrastructure’ in your project root and add a class called ‘CustomDate’. Add the following code to this class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace ModelBindingValidation.Infrastructure
{
    public class CustomDate : Attribute, IModelValidator
    {
        public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
        {

            if (Convert.ToDateTime(context.Model) > DateTime.Now)
                return new List<ModelValidationResult> {
                    new ModelValidationResult("", "Date of Birth cannot be in the future")
                };
            else if (Convert.ToDateTime(context.Model) < new DateTime(1980, 1, 1))
                return new List<ModelValidationResult> {
                    new ModelValidationResult("", "Date of Birth should not be before 1980")
                };
            else
                return Enumerable.Empty<ModelValidationResult>();
        }
    }
}

In the Validate method I am performing validation for the date by checking 2 conditions:

1. If it is in the future.
2. If it is before 1980.

If any of the above 2 condition fails then I am creating a new ModelValidationResult object and returning it in a list.

The first argument of ModelValidationResult constructor is the name of the property to which the error is associated. It is specified as the empty string when validating individual properties. The second argument is the Error message in string format.

If both the conditions passes then I am returning empty enemurable object – return Enumerable.Empty(); .

Now your custom validation attribute is set to be applied, so add this attribute to the DOB field as shown below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using ModelBindingValidation.Infrastructure;

namespace ModelBindingValidation.Models
{
    public class JobApplication
    {
        [Required]
        [Display(Name = "Job applicant name")]
        public string Name { get; set; }

        [CustomDate]
        public DateTime DOB { get; set; }

        [Required(ErrorMessage = "Please select your sex")]
        public string Sex { get; set; }

        [Range(0, 5)]
        public int Experience { get; set; }

        [Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the Terms")]
        public bool TermsAccepted { get; set; }

        [RegularExpression("^[a-zA-Z0-9_\\.-][email protected]([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "E-mail is not valid")]
        public string Email { get; set; }
    }
}

And remove your custom validation code for the DOB field from the action method:

[HttpPost]
public IActionResult Index(JobApplication jobApplication)
{
    if (ModelState.IsValid)
        return View("Accepted", jobApplication);
    else
        return View();
}

Now test your DOB validation which will work just fine. The below image shows the error message for date that is before the year 1980:

custom model validation for date

In the same way I can create validation for names preventing some user with a particular name. For this create a new class ‘NameValidate.cs’ and add the following code to it:

using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ModelBindingValidation.Infrastructure
{
    public class NameValidate : Attribute, IModelValidator
    {
        public string[] NotAllowed { get; set; }
        public string ErrorMessage { get; set; }
        public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
        {

            if (NotAllowed.Contains(context.Model as string))
                return new List<ModelValidationResult> {
                    new ModelValidationResult("", ErrorMessage)
                };
            else
                return Enumerable.Empty<ModelValidationResult>();
        }
    }
}

Important thing to note is that when applying the [[NameValidate]] attribute you have to provide the values for the properties:

1. ErrorMessage – the error message in string format.
2. NotAllowed – a string array for names you like to prevent.

Now simply add the attribute to your field like this:

[Required]
[Display(Name = "Job applicant name")]
[NameValidate(NotAllowed = new string[] { "Osama Bin Laden", "Saddam Hussain", "Mohammed Gaddafi" }, ErrorMessage = "You cannot apply for the Job")]
public string Name { get; set; }

With this attribute applied, people like “Osama Bin Laden”, “Saddam Hussain”, “Mohammed Gaddafi” will not be able to apply for the job.

The image given below shown the validation error message when name is Saddam Hussain:

custom model validation for name

Client-Side Validation

Till now the Model Validations which you have applied are Server-Side Validations. This means when form is submitted the control values go to server where it is validated. The server then sends back the result with is then shown in the View. This take a few milliseconds time to complete.

If you want instant validation results then you have to use the Client-Side Validation, which happens on the browser itself and there is no server side actions on it. This type of validation is also called as unobtrusive client-side validation and is done by jQuery with is a library of JavaScript.

In order to do Client-Side Validation add 3 files to your application:

1. jQuery
2. jQuery Validation
3. jQuery Validation Unobtrusive

Simple add then to your bower.json file and they will download automatically to your application, like shown below:

{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "v4.1.3",
    "jQuery": "3.3.1",
    "jquery-validation": "1.17.0",
    "jquery-validation-unobtrusive": "v3.2.10"
  }
}

Now reference these 3 files to your view like as shown in the code below:

@model JobApplication
@{
    Layout = "_Layout";
    ViewData["Title"] = "Job Application";
}
@section scripts {
    <script src="/lib/jquery/dist/jquery.min.js"></script>
    <script src="/lib/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js">
    </script>
}

<h2>Job Application</h2>

<div asp-validation-summary="ModelOnly" class="text-danger"></div>  
<form class="m-1 p-1" method="post">
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="DOB"></label>
        <input asp-for="DOB" type="text" asp-format="{0:d}" class="form-control" />
        <span asp-validation-for="DOB" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Email"></label>
        <input asp-for="Email" type="text" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Sex"></label>
        <div>
            <input asp-for="Sex" type="radio" value="M" />Male
            <input asp-for="Sex" type="radio" value="F" />Female
        </div>
        <span asp-validation-for="Sex" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Experience"></label>
        <select asp-for="Experience" class="form-control">
            <option value="Select">Select</option>
            <option value="0">Fresher</option>
            <option value="1">0-1 years</option>
            <option value="2">1-2 years</option>
            <option value="3">2-3 years</option>
            <option value="4">3-4 years</option>
            <option value="5">4-5 years</option>
        </select>
        <span asp-validation-for="Experience" class="text-danger"></span>
    </div>
    <div class="form-group">
        <input asp-for="TermsAccepted" />
        <label asp-for="TermsAccepted" class="form-check-label">
            I accept the terms & conditions
        </label>
        <span asp-validation-for="TermsAccepted" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Submit Application</button>
</form>

<style>
    .input-validation-error {
        border-color: red;
    }
</style>

Now go to the Index View and check how client-side validation gives instant results without sending a request to the server.

The Client-Side validations cannot work property for Range validation attribute dealing with bool. Therefore you can comment out the Range attribute and use a custom validation attribute for your terms checkbox.

See below code:

//[Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the Terms")]
public bool TermsAccepted { get; set; }

Also note that the Client-Side Validation will not work for your Custom Model Validation Attribute like the [CustomDate] and [NameValidate].

Remote Validations

Remote Validations are asynchronous validations, although look like Client-Side validations, but are done on the server by AJAX. The Remove Validations are performed in the background and the user doesn’t have to click the submit button and then wait for a new view to be rendered and returned.

To create a Remote Validation you need to add an action method that returns JsonResult and has a parameter whose name is the property name it is applied to.

Let me apply a Remove Validation on the DOB property, so add a new action ‘ValidDate’ on the Job controller. The code is given below:

public JsonResult ValidateDate(DateTime DOB)
{
    if (DOB > DateTime.Now)
        return Json("Date of Birth cannot be in the future");
    else if (DOB < new DateTime(1980, 1, 1))
        return Json("Date of Birth should not be before 1980");
    else
        return Json(true);
}

It is checking if the date is in the future or before year 1980, in that case returning error in JSON format. If date is proper then it returns ‘true’ in Json format.

Now comment out the [[CustomDate]] attribute on the JobApplicaiton.cs and add the [[Remote]] attribute as shown below:

//[CustomDate]
[Remote("ValidateDate", "Job")]
public DateTime DOB { get; set; }

You can check it by typing in the DOB field and will notice the Remote validation will be called on every keypress event.

The below image shows the error message returned from the remote validation:

custom model validation for date

You can download the source code using the below link:

Download

Conclusion

In this tutorial I explained you all about the Model Binding and Model Validation techniques. I hope you enjoyed learning them.

Share this article -

yogihosting

ABOUT THE AUTHOR

This article has been written by the Technical Staff of YogiHosting. Check out other articles on "ASP.NET Core, jQuery, EF Core, SEO, jQuery, HTML" and more.