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: