Custom Validation Attributes in ASP.NET Core MVC

How to Create Custom Validation Attribute in ASP.NET Core MVC

In this article, I will discuss How to Create Custom Validation Data Annotation Attributes in ASP.NET Core MVC Applications with Real-Time Examples. Please read our previous article discussing Data Annotation Attributes in ASP.NET Core MVC.

What is Custom Data Annotation in ASP.NET Core MVC?

Custom Data Annotations in ASP.NET Core MVC allow developers to create their own validation attributes when the built-in validation attributes (like Required, Range, MaxLength, etc.) do not meet the application’s specific needs. Custom Data Annotations provide a way to enforce specific business rules or validation logic by extending the default behavior of the MVC validation system. Some of the real-time examples of using Custom Data Annotation Attributes in ASP.NET Core MVC for validation are as follows:

  • Validating complex password rules (e.g., must contain uppercase, lowercase, digits, and special characters).
  • Ensure that a date is not a future date.
  • Ensuring a Date is between a Range of Dates,
Example to Understand Custom Validation Attribute in ASP.NET Core MVC:

We will use the same example we used in our previous article, where we created the following employee registration form. At this point, the DateOfJoining and DateOfBirth properties are decorated with built-in data annotation attributes to ensure the date is required and formatted correctly as a date. However, we also want to enforce that the date of joining should not be a future date, and DateOfBirth should ensure the employee’s age is between 18 and 60. These validation rules are not currently applied to the fields, as shown in the image below.

Example to Understand Custom Validation Attribute in ASP.NET Core MVC

Now, what we want is that when we provide a future date for the Date of Joining and a date that is not between 18 and 60 for the Date of Birth filed, we should display the following error message.

Example to Understand Custom Validation Attribute in ASP.NET Core MVC

To enforce additional validation rules for validating DateOfJoining and DateOfBirth as per our business requirements, we can create two custom data annotation validation attributes.

How Do We Create and Use Custom Validation Data Annotation in ASP.NET Core MVC?

To create and use a Custom Validation Attribute in ASP.NET Core MVC, we need to:

  • Define a Custom Validation Attribute: This involves creating a new class that inherits from ValidationAttribute and overrides the IsValid method. Optionally, implement client-side validation by creating an adapter.
  • Apply the Custom Attribute to Model Properties: Once the custom attribute is defined, you can apply it to relevant properties within your model.

So, we are going to create the following two Custom Validation Attributes:

  • DateNotInFutureAttribute: This attribute will ensure that the provided Joining Date is not in the future.
  • AgeRangeAttribute: This attribute will ensure that the age calculated from the provided DateOfBirth falls between 18 and 60 years.

Let us proceed and see how to create the above two Custom Data Annotation Attributes. First, create a folder called ValidationAttributes in the project root directory, where we will create the Custom Validation Attributes.

DateNotInFutureAttribute

Create a class file named DateNotInFutureAttribute.cs within the ValidationAttributes folder, and then copy and paste the following code. This custom validation attribute ensures that a provided DateTime value (such as a “Date of Joining”) is not set to a future date. This is useful in scenarios where dates should logically be past or present, such as employee joining dates, birthdates, or events that have already occurred. If the date provided is in the future, it triggers a validation error.

using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.ValidationAttributes
{
    //Validates that the provided date is not in the future.
    public class DateNotInFutureAttribute : ValidationAttribute
    {
        // Constructor that sets the default error message.
        public DateNotInFutureAttribute()
        {
            // Set a default error message to be displayed when validation fails.
            ErrorMessage = "Date of Joining cannot be in the future.";
        }

        // Overrides the IsValid method to implement custom validation logic.
        // value: The value of the property being validated
        // validationContext: The Context information about the validation operation
        // ValidationResult indicating whether validation succeeded or failed
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            // If the value is null, assume that another attribute (e.g., [Required]) handles it.
            if (value == null)
                return ValidationResult.Success;

            // Check if the value is of type DateTime.
            if (value is DateTime dateValue)
            {
                // Compare the provided date with the current date and time.
                if (dateValue > DateTime.Now)
                {
                    // If the date is in the future, return a validation error with the specified message.
                    return new ValidationResult(ErrorMessage);
                }
            }
            else
            {
                // If the value is not a DateTime, return a validation error indicating improper usage.
                return new ValidationResult("Invalid data type for DateNotInFutureAttribute.");
            }

            // If all checks pass, return success.
            return ValidationResult.Success;
        }
    }
}
AgeRangeAttribute

Create a class file named AgeRangeAttribute.cs within the ValidationAttributes folder, and then copy and paste the following code. This Custom Validation Attribute ensures that an employee’s age, derived from their date of birth, falls within a specified minimum and maximum range (e.g., 18 to 60 years). This is useful in scenarios such as employee onboarding systems, registration forms, or any application requiring age restrictions. A validation error will be triggered if the age is outside this range.

using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.ValidationAttributes
{
    // Validates that the age derived from the date of birth is within a specified range.
    public class AgeRangeAttribute : ValidationAttribute
    {
        // Private fields to store the minimum and maximum age limits.
        private readonly int _minAge;
        private readonly int _maxAge;

        // Constructor that initializes the minimum and maximum age limits.
        public AgeRangeAttribute(int minAge, int maxAge)
        {
            // Validate that the minimum age is not negative.
            if (minAge < 0)
                throw new ArgumentOutOfRangeException(nameof(minAge), "Minimum age cannot be negative.");

            // Validate that the maximum age is not less than the minimum age.
            if (maxAge < minAge)
                throw new ArgumentOutOfRangeException(nameof(maxAge), "Maximum age cannot be less than minimum age.");

            // Assign the validated age limits to the private fields.
            _minAge = minAge;
            _maxAge = maxAge;

            // Set a default error message that includes the age range.
            ErrorMessage = $"Age must be between {minAge} and {maxAge} years.";
        }

        // Overrides the IsValid method to implement custom age range validation.
        // value: The value of the property being validated (expected to be DateTime).
        // validationContext: The Context information about the validation operation
        // ValidationResult indicating whether validation succeeded or failed.
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            // Check if the value is of type DateTime (i.e., date of birth).
            if (value is DateTime dateOfBirth)
            {
                // Calculate the preliminary age by subtracting the birth year from the current year.
                var age = DateTime.Now.Year - dateOfBirth.Year;

                // Adjust the age if the birthday hasn't occurred yet this year.
                if (dateOfBirth > DateTime.Now.AddYears(-age))
                {
                    age--;
                }

                // Check if the calculated age is outside the specified range.
                if (age < _minAge || age > _maxAge)
                {
                    // Return a validation error with a message that includes the allowed age range.
                    return new ValidationResult($"Employee age must be between {_minAge} and {_maxAge} years.");
                }
            }
            else
            {
                // If the value is not a DateTime, you might want to handle it accordingly.
                // For simplicity, we'll assume it's valid in this case.
                // Alternatively, you could return a ValidationResult indicating invalid usage.
            }

            // If all checks pass, return success.
            return ValidationResult.Success;
        }
    }
}
What is ValidationContext?

ValidationContext is a class provided by the System.ComponentModel.DataAnnotations namespace. It encapsulates contextual information about the validation operation being performed. When a validation attribute’s IsValid method is invoked, ASP.NET Core MVC passes an instance of ValidationContext to provide additional details about the object and property being validated. The following are some of the most commonly used properties and methods of ValidationContext:

  • ObjectInstance: The actual object instance that is being validated. This allows you to access other properties of the model within your validation logic.
  • ObjectType: The type of the object being validated.
  • MemberName: The name of the member (property) being validated.
  • DisplayName: The display name of the member, which can be customized using the [Display] attribute.
Apply the Attribute to a Model Property

Now that we have created the Custom Validation Attributes, we can apply them to the respective properties of the Employee View Model. So, modify the EmployeeViewModel.cs class file as follows. We have applied the Custom DateNotInFutureAttribute to the DateOfJoining property and AgeRangeAttribute on the DateOfBirth property.

using DataAnnotationsDemo.ValidationAttributes;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.ComponentModel.DataAnnotations;

namespace DataAnnotationsDemo.Models.ViewModels
{
    public class EmployeeViewModel
    {
        // Basic Information
        [Required(ErrorMessage = "First Name is required")]
        [Display(Name = "First Name")]
        [StringLength(30, MinimumLength = 2, ErrorMessage = "First name should be between 2 and 30 characters")]
        public string FirstName { get; set; }

        [Required(ErrorMessage = "Last Name is required")]
        [StringLength(30, ErrorMessage = "Last name cannot exceed 30 characters")]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }

        [Required(ErrorMessage = "Email is required")]
        [EmailAddress(ErrorMessage = "Invalid Email Address")]
        public string Email { get; set; }

        [Required(ErrorMessage = "Date of Birth is required")]
        [DataType(DataType.Date)]
        [Display(Name = "Date Of Birth")]
        //Applying Custom AgeRange Validation Attribute
        [AgeRange(18, 60, ErrorMessage = "Employee must be between 18 and 60 years old.")]
        public DateTime? DateOfBirth { get; set; }

        [Required(ErrorMessage = "Joining Date is required")]
        [DataType(DataType.Date)]
        //Applying Custom DateNotInFuture Validation Attribute
        [DateNotInFuture(ErrorMessage = "Joining date cannot be in the future.")]
        [Display(Name = "Joining Date")]
        public DateTime? JoiningDate { get; set; }

        [Required(ErrorMessage = "Gender is required")]
        public Gender? Gender { get; set; }

        // Address Information
        [Required(ErrorMessage = "Street is required")]
        [StringLength(100, ErrorMessage = "Street cannot exceed 100 characters")]
        public string Street { get; set; }

        [Required(ErrorMessage = "City is required")]
        [StringLength(50, ErrorMessage = "City cannot exceed 50 characters")]
        public string City { get; set; }

        [Required(ErrorMessage = "State is required")]
        [StringLength(50, ErrorMessage = "State cannot exceed 50 characters")]
        public string State { get; set; }

        [Required(ErrorMessage = "Postal Code is required")]
        [RegularExpression(@"^\d{5}(-\d{4})?$|^\d{6}$", ErrorMessage = "Invalid Postal Code")]
        [Display(Name = "Postal or Zip Code")]
        public string PostalCode { get; set; }

        // Job Details
        [Required(ErrorMessage = "Job Title is required")]
        [Display(Name = "Job Title")]
        public int SelectedJobTitleId { get; set; }

        [Required(ErrorMessage = "Department is required")]
        [Display(Name = "Department")]
        public int DepartmentId { get; set; }

        [DataType(DataType.Currency)]
        [Range(30000, 200000, ErrorMessage = "Salary must be between 30,000 and 200,000")]
        public decimal Salary { get; set; }

        // Skills
        [Display(Name = "Skills")]
        public List<int> SkillSetIds { get; set; }

        // Account Information
        [Required(ErrorMessage = "Password is required")]
        [DataType(DataType.Password)]
        [StringLength(100, MinimumLength = 6, ErrorMessage = "Password should be at least 6 characters")]
        public string Password { get; set; }

        [Required(ErrorMessage = "Confirm Password is required")]
        [DataType(DataType.Password)]
        [Compare("Password", ErrorMessage = "Passwords do not match")]
        [Display(Name = "Confirm Password")]
        public string ConfirmPassword { get; set; }

        // Lists for Dropdowns and Radio Buttons
        public IEnumerable<SelectListItem>? Departments { get; set; }
        public IEnumerable<SelectListItem>? SkillSets { get; set; }
        public IEnumerable<Gender>? Genders { get; set; }
        public IEnumerable<SelectListItem>? JobTitles { get; set; }
    }
}

Note: No Changes are required in Controller and Views.

Test Your Application

Test the application to ensure the custom validation attribute works as expected. Try submitting a form with a future date for the “Date of Joining” field and a Date of Birth with a value that does not fit between the ages of 18 and 60, and click on the submit button. You should see the following error message, as shown in the image below.

How to Create Custom Validation Attribute in ASP.NET Core MVC

Following these steps, we have successfully created and applied custom data annotation attributes in ASP.NET Core MVC, specifically validating the Date of Joining and date of Birth properties. This not only ensures data integrity but also enhances the robustness of your application. Validation is only performed on the server side, not on the client side. Let us proceed to see how to enable client-side validation.

Before understanding how to enable Client-Side Validation in ASP.NET Core MVC for a Custom Data Annotation Attribute, let’s first understand how client-side validation works in ASP.NET Core MVC when using Data Annotation.

How Client-Side Validation Works in ASP.NET Core MVC?

When we use Razor syntax to create form fields bound to model properties and those properties have Data Annotation attributes applied (such as Required, Range, etc.), the ASP.NET Core MVC framework automatically generates HTML elements with specific data-* attributes. These attributes define the validation rules applied to the input fields.

The jQuery Validate Unobtrusive library, which must be included in ASP.NET Core MVC projects, reads these data-* attributes and uses them to perform client-side validation.

Steps in Client-Side Validation:

Let us understand how Client-Side validation works step by step:

Model Properties with Data Annotations:

When we apply a Data Annotation (e.g., Required, StringLength, etc.) to a model property, ASP.NET Core automatically generates HTML elements that contain the necessary validation attributes to represent the validation rules for that property. Let us take the following class as an example. Here, the Required Data Annotation is applied to the JoiningDate property to enforce that the field is mandatory.

public class EmployeeViewModel
{
    [Required(ErrorMessage = "Date of Joining is required")]
    public DateTime? JoiningDate { get; set; }
}
Using Tag Helpers or HTML Helper Methods in the Razor View:

In Razor view, we can use the asp-for tag helper (or HTML helper methods) to bind form elements to model properties, allowing ASP.NET Core to generate the required HTML and validation attributes. For example, we can write the following Tag Helpers in our Razor view:

<input asp-for="JoiningDate" class="form-control" type="date" />
<span asp-validation-for="JoiningDate" class="text-danger"></span>
Generated HTML with Validation Attributes:

When the Razor view is rendered, the asp-for tag helper generates the following HTML with validation-specific attributes. To check the same, run the application and view the Page source:

<input class="form-control" type="date" 
       data-val="true" 
       data-val-required="Joining Date is required" 
       id="JoiningDate" 
       name="JoiningDate" 
       value="">
<span class="text-danger field-validation-valid" 
      data-valmsg-for="JoiningDate" 
      data-valmsg-replace="true"></span>
Understanding the Validation Attributes:

Let’s break down the key validation-related attributes that are automatically added:

  • data-val=”true”: This attribute tells jQuery Unobtrusive Validation that this input field has validation rules that need to be enforced. It signals the presence of validation logic.
  • data-val-required=”Date of Joining is required”: This specifies the validation rule for the required field. The value “Date of Joining is required” is the error message displayed if the user submits the form without filling in this field.
  • data-valmsg-for=”JoiningDate”: This attribute links the span element to the JoiningDate input field. It serves as a container for the validation error message related to that field. When validation fails, the message will be displayed inside this span.
  • data-valmsg-replace=”true”: This attribute indicates whether the entire span content should be replaced with the validation error message. If set to true, the error message will replace any existing content in the span. If false, the error message will be appended to the current content of the span.
Role of jQuery Validate Unobtrusive:

The jQuery Validate Unobtrusive library reads the generated data-* attributes and integrates them with the jQuery Validate plugin, which handles client-side validation.

When the form is submitted, the plugin checks each input field against its associated validation rules (like required, range, etc.). If any field fails validation, the form will not be submitted. Instead, the corresponding error message will be displayed in the span element associated with the input field.

How to Enable Client-Side Validation for Custom Data Annotation Attribute in ASP.NET Core MVC:

To enable client-side validation for a Custom Data Annotation Attribute in ASP.NET Core MVC, we need to perform the following steps:

  • Modify the Custom Attribute to Implement the IClientModelValidator interface.
  • Implement the AddValidation Method to Add Custom Data Attributes based on which client validation will work.
  • Create Custom JavaScript Validation Logic, which will validate the data based on the data-val-* attributes on the client side.
  • Include the JavaScript in Your View or Layout where you want client-side validation.
  • Ensure jQuery Unobtrusive Validation Is Enabled.

Let us see how to modify the DateNotInFutureAttribute and AgeRangeAttribute to include client-side validation:

Modifying Custom DateNotInFutureAttribute:

First, we need to modify the custom attribute to implement the IClientModelValidator interface. This interface allows us to add custom data* attributes to our HTML elements, which client-side validation scripts can use. So, modify the DateNotInFutureAttribute as follows. Implementing IClientModelValidator enables our custom attribute to participate in the client-side validation process. The AddValidation method is where we add custom data attributes that our client-side scripts will use.

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

namespace DataAnnotationsDemo.ValidationAttributes
{
    // Validates that the provided date is not in the future.
    public class DateNotInFutureAttribute : ValidationAttribute, IClientModelValidator
    {
        // Constructor that sets the default error message.
        public DateNotInFutureAttribute()
        {
            // Set a default error message to be displayed when validation fails.
            ErrorMessage = "Date of Joining cannot be in the future.";
        }

        // Overrides the IsValid method to implement custom validation logic.
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            // If the value is null, assume that another attribute (e.g., [Required]) handles it.
            if (value == null)
                return ValidationResult.Success;

            // Check if the value is of type DateTime.
            if (value is DateTime dateValue)
            {
                // Compare the provided date with the current date and time.
                if (dateValue > DateTime.Now)
                {
                    // If the date is in the future, return a validation error with the specified message.
                    return new ValidationResult(ErrorMessage);
                }
            }
            else
            {
                // If the value is not a DateTime, return a validation error indicating improper usage.
                return new ValidationResult("Invalid data type for DateNotInFutureAttribute.");
            }

            // If all checks pass, return success.
            return ValidationResult.Success;
        }

        // Implement the AddValidation method to add client-side validation support.
        public void AddValidation(ClientModelValidationContext context)
        {
            // Add the 'data-val' attribute for client-side validation.
            MergeAttribute(context.Attributes, "data-val", "true");

            // Add a custom data attribute with the validation error message.
            MergeAttribute(context.Attributes, "data-val-datenotinfuture", ErrorMessage);
        }

        // Helper method to merge attributes into the HTML element's attributes.
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            // Adds the attribute if it doesn't already exist in the dictionary.
            if (attributes.ContainsKey(key))
                return false;

            attributes.Add(key, value);
            return true;
        }
    }
}
Code Explanation:

Implement IClientModelValidator: By implementing this interface, we can inject custom validation attributes into the rendered HTML, which are then used by client-side scripts.

AddValidation Method: This method adds the necessary data-* attributes to the HTML elements. The client-side validation framework (e.g., jQuery Validate) uses these attributes to perform validation.

  • data-val=”true”: Enables validation for the field.
  • data-val-datenotinfuture=”Error Message”: A custom attribute that holds the error message. The key datenotinfuture should match the name used in the client-side JavaScript file, which we will create in the next step.

MergeAttribute Helper Method: This method ensures that attributes are only added if they don’t already exist, preventing accidental overwrites.

Note: The ValidJoiningDateAttribute uses the ValidationAttribute (IsValid method) for server-side validation and the IClientModelValidator interface (AddValidation method) to add client-side validation metadata. Now, the ValidJoiningDateAttribute not only checks server-side validation but also enables client-side validation.

Create Custom JavaScript Validation Logic

We need to create a custom jQuery Validation method that corresponds to our custom data attribute. This method will contain the client-side logic to validate the date. Let us first understand the syntax, and then we will use the same:

jQuery.validator.addMethod:

This method defines a new validation rule (method) for jQuery Validate. It contains the logic determining whether the input is valid based on your custom criteria. The syntax is given below:

Syntax: jQuery.validator.addMethod(name, method, message);

  • name: A unique name for your custom validation method.
  • method: A function containing the validation logic. It should return true if the input is valid and false otherwise.
  • message: (Optional) A default error message if validation fails.
jQuery.validator.unobtrusive.adapters.add:

This method bridges the gap between the HTML data-* attributes generated by ASP.NET Core’s Unobtrusive Validation and the custom validation method defined by addMethod. It maps the HTML attributes to the parameters the custom validation method expects. The syntax is given below:

Syntax: jQuery.validator.unobtrusive.adapters.add(name, params, fn);

  • name: The name of the adapter, typically matching the data-val-<name> attribute.
  • params: An array of parameter names that your validation method expects.
  • fn: A function that sets up the validation rule and message (function).
DateNotInFutureValidation JavaScript File:

So, create a new JavaScript file named DateNotInFutureValidation.js inside the project’s wwwroot/js directory and add the following code. The following custom validator checks if the input date is not in the future relative to the current date. The adapter connects the custom data attribute (data-val-datenotinfuture) to the validation method.

// Custom validator for DateNotInFutureAttribute
$.validator.addMethod('datenotinfuture', function (value) {
    // If the value is empty, do not perform validation here (another validator should handle [Required])
    if (value) {
        // Parse the date value
        var inputDate = new Date(value);
        var today = new Date();

        // Set the time components to zero for accurate comparison
        inputDate.setHours(0, 0, 0, 0);
        today.setHours(0, 0, 0, 0);

        // Compare the input date with today
        return inputDate <= today;
    }
    return true;
});

// Associate the validator with the unobtrusive validation system.
// The addBool method is used when your custom validation attribute does not require additional parameters besides the error message.
// 'datenotinfuture' - This must match the name used in your data attributes (data-val-datenotinfuture).
$.validator.unobtrusive.adapters.addBool('datenotinfuture');

//Tells the unobtrusive validation framework to associate the data-val-datenotinfuture attribute with the 'datenotinfuture' validation method.
Code Explanation:
  • $.validator.addMethod: This method adds a new validation method called datenotinfuture. It checks whether the input date is greater than the current date. If it is, it returns false, triggering validation failure.
  • $.validator.unobtrusive.adapters.addBool: Associates this validation method with the [DateNotInFuture] attribute.
Modifying Custom AgeRangeAttribute:

Now, let us see how to validate the Custom Age Range attribute with client-side validation. So, first, modify your AgeRangeAttribute to implement the IClientModelValidator interface as follows. This allows your attribute to participate in client-side validation.

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

namespace DataAnnotationsDemo.ValidationAttributes
{
    // Validates that the age derived from the date of birth is within a specified range.
    public class AgeRangeAttribute : ValidationAttribute, IClientModelValidator
    {
        // Private fields to store the minimum and maximum age limits.
        private readonly int _minAge;
        private readonly int _maxAge;

        // Constructor that initializes the minimum and maximum age limits.
        public AgeRangeAttribute(int minAge, int maxAge)
        {
            // Validate that the minimum age is not negative.
            if (minAge < 0)
                throw new ArgumentOutOfRangeException(nameof(minAge), "Minimum age cannot be negative.");

            // Validate that the maximum age is not less than the minimum age.
            if (maxAge < minAge)
                throw new ArgumentOutOfRangeException(nameof(maxAge), "Maximum age cannot be less than minimum age.");

            // Assign the validated age limits to the private fields.
            _minAge = minAge;
            _maxAge = maxAge;

            // Set a default error message that includes the age range.
            ErrorMessage = $"Age must be between {minAge} and {maxAge} years.";
        }

        // Overrides the IsValid method to implement custom age range validation.
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            // Check if the value is of type DateTime (i.e., date of birth).
            if (value is DateTime dateOfBirth)
            {
                // Calculate the preliminary age by subtracting the birth year from the current year.
                var age = DateTime.Now.Year - dateOfBirth.Year;

                // Adjust the age if the birthday hasn't occurred yet this year.
                if (dateOfBirth > DateTime.Now.AddYears(-age))
                {
                    age--;
                }

                // Check if the calculated age is outside the specified range.
                if (age < _minAge || age > _maxAge)
                {
                    // Return a validation error with a message that includes the allowed age range.
                    return new ValidationResult($"Employee age must be between {_minAge} and {_maxAge} years.");
                }
            }
            else
            {
                // If the value is not a DateTime, return a validation error indicating improper usage.
                return new ValidationResult("Invalid data type for AgeRangeAttribute.");
            }

            // If all checks pass, return success.
            return ValidationResult.Success;
        }

        // Implement IClientModelValidator to add client-side validation support.
        public void AddValidation(ClientModelValidationContext context)
        {
            // Add the standard data-val attribute to enable client-side validation.
            MergeAttribute(context.Attributes, "data-val", "true");

            // Add a custom data-val-agerange attribute with the validation error message.
            MergeAttribute(context.Attributes, "data-val-agerange", ErrorMessage);

            // Add additional data attributes for minimum and maximum age.
            MergeAttribute(context.Attributes, "data-val-agerange-minage", _minAge.ToString());
            MergeAttribute(context.Attributes, "data-val-agerange-maxage", _maxAge.ToString());
        }

        // Helper method to merge attributes into the HTML element's attributes.
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            // Adds the attribute if it doesn't already exist in the dictionary.
            if (attributes.ContainsKey(key))
                return false;

            attributes.Add(key, value);
            return true;
        }
    }
}
Code Explanation:
  • AddValidation: This method adds the necessary HTML attributes (data-val, data-val-agerange, data-val-agerange-minage, data-val-agerange-maxage) to enable client-side validation. These data attributes allow the client-side script to access the necessary parameters and display appropriate error messages.
  • IClientModelValidator: This interface (AddValidation method) ensures that client-side validation is integrated with the server-side validation logic.
Create Custom JavaScript Validation Logic

Next, we need to create a custom jQuery Validation method that corresponds to our custom validation attribute. This method will contain the client-side logic to validate the date. So, create a new JavaScript file named agerangevalidation.js inside the project’s wwwroot/js directory and add the following code.

// Adds a custom validation method named 'agerange' to jQuery Validate.
// This method checks if the age derived from the input date falls within the specified range.
jQuery.validator.addMethod("agerange", function (value, element, params) {
    if (value) {

        // Parse the input value (date of birth) as a Date object.
        var dateOfBirth = new Date(value);
        var today = new Date();

        // Calculate the age based on the current date and input date.
        var age = today.getFullYear() - dateOfBirth.getFullYear();

        // Adjust the age if the birthday hasn't occurred yet this year.
        var monthDifference = today.getMonth() - dateOfBirth.getMonth();
        if (
            monthDifference < 0 ||
            (monthDifference === 0 && today.getDate() < dateOfBirth.getDate())
        ) {
            age--;
        }

        // Retrieve min and max age from parameters
        var minAge = parseInt(params.minage);
        var maxAge = parseInt(params.maxage);

        // Validate age range and return true if the age is within the specified range.
        return age >= minAge && age <= maxAge;
    }
    // If value is empty, let other validators handle it (e.g., [Required])
    return true;
});

// Registers an Unobtrusive Validation adapter for the 'agerange' method.
// This adapter maps the data attributes to the validation method's parameters.
jQuery.validator.unobtrusive.adapters.add(
    "agerange", // Must match the data-val-agerange attribute.
    ["minage", "maxage"], //  These correspond to data-val-agerange-minage and data-val-agerange-maxage in your HTML element
    function (options) {
        // Function to set up the validation rule and message
        // options.rules: An object where you define validation rules for the element.
        // "agerange": The key matching your custom validation method name.
        // Parameters: Pass minage and maxage to the validation method.
        options.rules["agerange"] = {
            minage: options.params.minage, // Extracted from data-val-agerange-minage
            maxage: options.params.maxage, // Extracted from data-val-agerange-maxage
        };
        options.messages["agerange"] = options.message;  //The error message extracted from data-val-agerange.
    }
);
Script Order:

It’s important to load custom validation scripts after the standard jQuery and jQuery Validate scripts to ensure the necessary libraries are available. So, please modify the _ValidationScriptsPartial partial view as follows to include the custom javascript files:

<!-- jQuery Validation Plugin -->
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<!-- jQuery Unobtrusive Validation -->
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<script src="~/js/datenotinfuturevalidation.js"></script>
<script src="~/js/agerangevalidation.js"></script>
Testing the Client-Side Validation:

Now, run the application and test the functionality, and it should work as expected, as shown in the below image:

How to Enable Client-Side Validation for Custom Data Annotation Attribute in ASP.NET Core MVC

When Should We Use Custom Data Annotation in ASP.NET Core MVC?

We need to Custom Data Annotations in ASP.NET Core MVC when:

  • The built-in validation attributes do not meet specific business requirements.
  • We need to enforce complex validation rules beyond simple checks like required fields or length constraints.
  • We want to encapsulate validation logic within an attribute, promoting code reuse and separation of concerns.
  • The validation rules involve multiple properties or require complex computations.

Custom Data Annotations in ASP.NET Core MVC provide a robust mechanism to enforce complex validation rules and encapsulate business logic within model attributes. By creating reusable and maintainable custom attributes, developers can enhance the integrity and reliability of their applications while maintaining clean and organized codebases.

In the next article, I will discuss Remote Validation in ASP.NET Core MVC Applications. In this article, I explain Custom Data Annotation in an ASP.NET Core MVC Application with Examples. I hope you enjoy this Custom Data Annotation in ASP.NET Core MVC article.

Leave a Reply

Your email address will not be published. Required fields are marked *