ASP.NET Core – Client-side custom validation attributes

I wrote about how to add custom validation attributes. These are used for model validation on the server-side. You can also use these for client-side validation, which I’ll show in this article.

1 – Implement IClientModelValidator

The first step is to implement the IClientModelValidator interface in the custom validation attribute class. This has a single method – AddValidation() – which you use to add data validation HTML attributes (not to be confused with C# attributes):

  • data-val=”true”.
  • data-val-futuredatetime=”<field name> must be a future date”.

Here’s an example of how to implement this method:

using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System.ComponentModel.DataAnnotations;

public class FutureDateTimeAttribute : ValidationAttribute, IClientModelValidator //1. add this interface
{
	public override bool IsValid(object value)
	{
		if (value is DateTimeOffset dateTimeOffset && dateTimeOffset > DateTimeOffset.Now)
			return true;

		return false;
	}
	public override string FormatErrorMessage(string name)
	{
		return $"{name} must be a future date";
	}
        //2. implement this method
	public void AddValidation(ClientModelValidationContext context)
	{
		var fieldName = context.Attributes["name"];
		context.Attributes.TryAdd("data-val", "true");
		context.Attributes.TryAdd("data-val-futuredatetime", FormatErrorMessage(fieldName));
	}
}
Code language: C# (cs)

Let’s say you’re using FutureDateTimeAttribute on a property called Showtime:

public class MovieTicketOrder
{
	public string Movie { get; set; }

	[FutureDateTime]
	public DateTimeOffset? Showtime { get; set; }
}
Code language: C# (cs)

When you add this property to a form with TagHelper, it’ll call AddValidation(), resulting in it adding the two HTML attributes:

<input 
data-val="true" 
data-val-futuredatetime="Showtime must be a future date" 
id="Showtime" name="Showtime" type="text" value="">
Code language: HTML, XML (xml)

2 – Add jQuery Validation function and adapter

MVC projects include jQuery Validation and jQuery Validation Unobtrusive for client-side validation by default. So the next step is to implement your validation logic in a jQuery Validation function and adapter, like this:

/* jQuery Validation function and adapter in /wwwroot/js/customValidator.js */

$.validator.addMethod('futuredatetime', function (value, element, params) {
    return new Date(value) > new Date(); //it's valid if it's a future date
});

$.validator.unobtrusive.adapters.add('futuredatetime', function (options) {
    options.rules['futuredatetime'] = [];
    options.messages['futuredatetime'] = options.message;
});
Code language: JavaScript (javascript)

When the user inputs a value, it validates the field with data-val-futuredatetime by calling the futuredatetime function. They’re linked together by name.

3 – Include the validation scripts

In /Views/Shared/_ValidationScriptsPartial.cshtml, add your custom validation script:

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

<script src="~/js/customValidator.js"></script>
Code language: HTML, XML (xml)

Finally, add _ValidationScriptsPartial to your View. Here is the View with _ValidationScriptsPartial loaded at the end:

@model MoviesMVC.Models.MovieTicketOrder


@using (Html.BeginForm())
{
    <div>
        <div>
            @Html.LabelFor(m => m.Movie)
            @Html.TextBoxFor(m => m.Movie)
            @Html.ValidationMessageFor(m => m.Movie)
        </div>
        <div>
            @Html.LabelFor(m => m.Showtime)
            @Html.TextBoxFor(m => m.Showtime)
            @Html.ValidationMessageFor(m => m.Showtime)
        </div>
        <div>
            <input type="submit" value="Add movie" />
        </div>
    </div>
}

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}
Code language: HTML, XML (xml)

Here’s what this looks like in the browser when you enter an invalid date:

Showing client-side validation error on the form: "Showtime must be a future date"