Custom Data Annotation Real-Time Examples in ASP.NET Core MVC

Custom Data Annotation Attribute Real-Time Examples in ASP.NET Core MVC

In this article, I will discuss Custom Data Annotation Attribute Real-Time Examples in ASP.NET Core MVC. Please read our previous article discussing How to Create Custom Data Annotation Attributes in ASP.NET Core MVC Applications. Let’s understand a few real-world scenarios where custom data annotations might come in handy:

Date of Birth Validation:

Suppose you’re building a website where users need to be at least 18 years old to register. You can create a custom validation attribute to ensure this. To create a custom data annotation attribute in ASP.NET Core MVC that validates whether the Date of Birth (DOB) indicates an age greater than 18 years, you’ll need to follow a few steps. These include creating a custom validation attribute, enabling client-side validation, and applying it to your model.

Create the Custom Validation Attribute

You need to create a custom attribute class that inherits from the ValidationAttribute and IClientModelValidator interface and overrides the IsValid method, which is used for Server Side Validation. Also, the AddValidation method of the IClientModelValidator interface, which is required for Client-Side Validation, must be implemented. So, create a class file named MinimumAgeAttribute.cs and copy and paste the following code.

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

namespace DataAnnotationsDemo.Models
{
    public class MinimumAgeAttribute : ValidationAttribute, IClientModelValidator
    {
        private readonly int _minimumAge;

        public MinimumAgeAttribute(int minimumAge)
        {
            _minimumAge = minimumAge;
            ErrorMessage = $"You must be at least {minimumAge} years old.";
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (value == null)
            {
                return new ValidationResult("Date of Birth is required.");
            }

            if (value is DateTime)
            {
                DateTime dateOfBirth = (DateTime)value;
                if (dateOfBirth > DateTime.Now.AddYears(-_minimumAge))
                {
                    return new ValidationResult(ErrorMessage);
                }
            }
            else
            {
                return new ValidationResult("Invalid date format.");
            }

            return ValidationResult.Success;
        }

        public void AddValidation(ClientModelValidationContext context)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-minimumage", ErrorMessage);
            MergeAttribute(context.Attributes, "data-val-minimumage-minage", _minimumAge.ToString());
        }

        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (!attributes.ContainsKey(key))
            {
                attributes.Add(key, value);
                return true;
            }
            return false;
        }
    }
}
Create a JavaScript File for Client-Side Validation

Next, we need to write JavaScript that works with the jQuery validation library to add a method for handling your custom MinimumAge validation. So, create a JavaScript file named minimum-age-validation.js within the wwwroot/js folder and copy and paste the following code.

// minimum-age-validation.js
jQuery.validator.addMethod("minimumage", function (value, element, params) {
    if (!value) {
        return true; // Not validating emptiness here, required attribute should handle this
    }
    var dateOfBirth = new Date(value);
    var minimumAgeDate = new Date();
    minimumAgeDate.setFullYear(minimumAgeDate.getFullYear() - parseInt(params));

    return dateOfBirth <= minimumAgeDate;
}, function (params, element) {
    return $(element).data('val-minimumage');
});

jQuery.validator.unobtrusive.adapters.add("minimumage", ["minage"], function (options) {
    options.rules["minimumage"] = options.params.minage;
    options.messages["minimumage"] = options.message;
});
Apply the Attribute to Your Model

Next, apply this attribute to the DOB property in your model. So, let us create a class file named Employee.cs and then copy and paste the following code:

using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
{
    public class Employee
    {
        [Required(ErrorMessage = "Date of Birth is required")]
        [DataType(DataType.Date, ErrorMessage = "Invalid Date Format")]
        [MinimumAge(18, ErrorMessage = "You must be at least 18 years old.")]
        public DateTime DateOfBirth { get; set; }
    }
}
Use the Model in Your MVC Controller

Ensure your MVC controller is set up to use model validation. So, modify the Employee Controller as follows:

using DataAnnotationsDemo.Models;
using Microsoft.AspNetCore.Mvc;
namespace DataAnnotationsDemo.Controllers
{
    public class EmployeeController : Controller
    {
        public IActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Create(Employee employee)
        {
            if (ModelState.IsValid)
            {
                //Save the Data into the Database
                //Redirect to a Different View
                return RedirectToAction("Successful");
            }

            return View(employee);
        }

        public string Successful()
        {
            return "Employee Added Successfully";
        }
    }
}
Update the View to Display Validation Messages

In your view, ensure you display validation messages related to the DOB field. So, modify the Create.cshtml view as follows:

@model DataAnnotationsDemo.Models.Employee

@{
    ViewData["Title"] = "Create";
}

<h1>Create Employee</h1>

<div class="row">
    <form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">
        
        <div class="form-group row">
            <label asp-for="DateOfBirth" class="col-sm-2 col-form-label"></label>
            <div class="col-sm-10">
                <input asp-for="DateOfBirth" class="form-control">
                <span asp-validation-for="DateOfBirth" class="text-danger"></span>
            </div>
        </div>

        <div class="form-group row">
            <div class="col-sm-10">
                <button type="submit" class="btn btn-primary">Create</button>
            </div>
        </div>
    </form>
</div>
<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/jquery.validate.unobtrusive.min.js"></script>
<script src="~/js/minimum-age-validation.js"></script>
Testing:

Now, run the application and try to enter an Age that is less than 18 years, and then you will get the required error message as shown in the image below:

Custom Data Annotation Real-Time Examples in ASP.NET Core MVC

Password Strength Checker:

Nowadays, most applications need users to set strong passwords. We can achieve this very easily using Custom Data Annotation Attribute in ASP.NET Core MVC Application. We are going to enforce the following rules on the password:

Password should contain uppercase, lowercase, numbers, and special characters (@, #, $, &), minimum 8 Characters

Create the Custom Validation Attribute

First, we will define the custom attribute by inheriting from the ValidationAttribute class for server-side validation and implementing the IClientModelValidator interface for client-side validation. So, create a class file named PasswordStrengthAttribute.cs and then copy and paste the following code:

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

namespace DataAnnotationsDemo.Models
{
    public class PasswordStrengthAttribute : ValidationAttribute, IClientModelValidator
    {
        public PasswordStrengthAttribute()
        {
            ErrorMessage = "Password must contain at least one uppercase letter, one lowercase letter, one number, one special character (@,#,$,&) and be at least 8 characters long.";
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (value == null || string.IsNullOrEmpty(value.ToString()))
            {
                return new ValidationResult("Password is required.");
            }

            var password = value.ToString();
            var regex = new Regex(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@#$&])[A-Za-z\d@#$&]{8,}$");

            if (!regex.IsMatch(password))
            {
                return new ValidationResult(ErrorMessage);
            }

            return ValidationResult.Success;
        }

        public void AddValidation(ClientModelValidationContext context)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            context.Attributes.Add("data-val", "true");
            context.Attributes.Add("data-val-passwordstrength", ErrorMessage);
        }
    }
}
Explanation

Regular Expression: The Regex pattern checks for:

  • (?=.*[a-z]): At least one lowercase letter.
  • (?=.*[A-Z]): At least one uppercase letter.
  • (?=.*\d): At least one digit.
  • (?=.*[@#$&]): At least one special character from the set @, #, $, &.
  • [A-Za-z\d@#$&]{8,}: The password must be at least 8 characters long and contain only the characters specified (no other special characters are allowed).

Server-side Validation: The IsValid method implements the server-side validation logic, using the regex pattern to validate the password string.

Client-side Validation: The AddValidation method adds the necessary data attributes to enable client-side validation using ASP.NET Core’s unobtrusive validation.

Add Client-Side Validation Script

Next, you need to write custom JavaScript that works with the jQuery validation library to add a method for handling your custom Password validation. So, create a JavaScript file named Password-Strength-Validation.js within the wwwroot/js folder and copy and paste the following code.

jQuery.validator.addMethod('passwordstrength', function (value, element) {
    if (!value) {
        return true; // Don't validate empty values
    }

    return this.optional(element) || /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@#$&])[A-Za-z\d@#$&]{8,}$/.test(value);
}, 'Password must contain at least one uppercase letter, one lowercase letter, one number, one special character (@,#,$,&) and be at least 8 characters long.');

jQuery.validator.unobtrusive.adapters.add('passwordstrength', function (options) {
    options.rules['passwordstrength'] = {};
    options.messages['passwordstrength'] = options.message;
});
Usage in a Model

Apply the custom validation attribute to a property in your model. Please modify the Employee model as follows:

using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
{
    public class Employee
    {
        [Required(ErrorMessage = "Date of Birth is required")]
        [DataType(DataType.Password)]
        [PasswordStrength]
        public string Password { get; set; }
    }
}
Update the View to Display Validation Messages

In your view, ensure you display validation messages related to the Password field. So, modify the Create.cshtml view as follows:

@model DataAnnotationsDemo.Models.Employee

@{
    ViewData["Title"] = "Create";
}

<h1>Create Employee</h1>

<div class="row">
    <form asp-controller="Employee" asp-action="Create" method="post" class="mt-3">
        
        <div class="form-group row">
            <label asp-for="Password" class="col-sm-2 col-form-label"></label>
            <div class="col-sm-10">
                <input asp-for="Password" class="form-control">
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
        </div>

        <div class="form-group row">
            <div class="col-sm-10">
                <button type="submit" class="btn btn-primary">Create</button>
            </div>
        </div>
    </form>
</div>
<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/jquery.validate.unobtrusive.min.js"></script>
<script src="~/js/minimum-age-validation.js"></script>
Testing:

Now, run the application and try to enter an invalid password and then you should get the following error message as shown in the below image:

Custom Data Annotation Real-Time Examples in ASP.NET Core MVC

Date Range Checker:

Creating a custom data annotation attribute in ASP.NET Core MVC to validate a date range involving both client-side and server-side validation can be very useful. Let us create a Custom Data Annotation Attribute with the following Rules:

  • From and ToDate should not be greater than Current Date
  • ToDate should not be greater than From Date
  • ToDate is mandatory if the User Selects the From Date

Create the Custom Validation Attribute

We will start by creating a custom attribute that can be used on a view model. This attribute will handle the server-side validation. First, we will define the custom attribute by inheriting from the ValidationAttribute class for server-side validation and implementing the IClientModelValidator interface for client-side validation. So, create a class file named DateRangeValidationAttribute.cs and then copy and paste the following code:

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

namespace DataAnnotationsDemo.Models
{
    public class DateRangeValidationAttribute : ValidationAttribute, IClientModelValidator
    {
        public string OtherPropertyName { get; set; }

        public DateRangeValidationAttribute(string otherPropertyName)
        {
            OtherPropertyName = otherPropertyName;
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherPropertyName);
            if (otherPropertyInfo == null)
            {
                return new ValidationResult($"Unknown property: {OtherPropertyName}");
            }

            var otherDate = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null) as DateTime?;
            var thisDate = value as DateTime?;

            if (thisDate.HasValue && thisDate > DateTime.Today)
            {
                return new ValidationResult("Date cannot be in the future.");
            }

            if (otherDate.HasValue && thisDate.HasValue)
            {
                if (OtherPropertyName == "FromDate" && thisDate < otherDate)
                {
                    return new ValidationResult("To date cannot be earlier than from date.");
                }
                else if (OtherPropertyName == "ToDate" && thisDate > otherDate)
                {
                    return new ValidationResult("From date cannot be later than to date.");
                }
            }

            return ValidationResult.Success;
        }

        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-daterange", ErrorMessage ?? "Date is out of the allowed range.");
            MergeAttribute(context.Attributes, $"data-val-daterange-other", OtherPropertyName);
        }

        private void MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (!attributes.ContainsKey(key))
            {
                attributes.Add(key, value);
            }
        }
    }
}
Usage in a Model

Next, apply this attribute to your model. This example assumes that your model has properties FromDate and ToDate. Let us modify the Employee.cs class file as follows. We want to find the employees who joined the organization between these dates.

using System.ComponentModel.DataAnnotations;
namespace DataAnnotationsDemo.Models
{
    public class Employee
    {
        [DateRangeValidation("ToDate", ErrorMessage = "From date cannot be in the future and must be before To date.")]
        public DateTime? FromDate { get; set; }

        [DateRangeValidation("FromDate", ErrorMessage = "To date is required if From date is selected and cannot be in the future or before From date.")]
        public DateTime? ToDate { get; set; }
    }
}
Add Client-Side Validation Script

Next, you need to write custom JavaScript that works with the jQuery validation library to add a method for handling your custom Date Rangle validation. So, create a JavaScript file named Date-Range-Validation.js within the wwwroot/js folder and copy and paste the following code.

$.validator.addMethod('daterange', function (value, element, params) {
    var otherPropName = $(element).data('valDaterangeOther');
    var otherValue = $('#' + otherPropName).val();
    if (otherPropName && otherValue) {
        var thisDate = new Date(value);
        var otherDate = new Date(otherValue);
        if (otherPropName === 'FromDate') {
            return thisDate >= otherDate && thisDate <= Date.now();
        } else {
            return thisDate <= otherDate && thisDate <= Date.now();
        }
    }
    return true;
}, function (params, element) {
    return $(element).data('valDaterange');
});

$.validator.unobtrusive.adapters.add('daterange', [], function (options) {
    options.rules['daterange'] = true;
    options.messages['daterange'] = options.message;
});
Update the View to Display Validation Messages

In your view, ensure you display validation messages related to the Password field. So, modify the Create.cshtml view as follows:

@model DataAnnotationsDemo.Models.Employee

@{
    ViewData["Title"] = "Create";
}

<h1>Create Employee</h1>

<div class="row">
    <form asp-controller="Employee" asp-action="Create" method="post" enctype="multipart/form-data" class="mt-3">
        
        <div class="form-group row">
            <label asp-for="FromDate" class="col-sm-2 col-form-label"></label>
            <div class="col-sm-10">
                <input asp-for="FromDate" class="form-control">
                <span asp-validation-for="FromDate" class="text-danger"></span>
            </div>
        </div>

        <div class="form-group row">
            <label asp-for="ToDate" class="col-sm-2 col-form-label"></label>
            <div class="col-sm-10">
                <input asp-for="ToDate" class="form-control">
                <span asp-validation-for="ToDate" class="text-danger"></span>
            </div>
        </div>

        <div class="form-group row">
            <div class="col-sm-10">
                <button type="submit" class="btn btn-primary">Create</button>
            </div>
        </div>
    </form>
</div>

<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/jquery.validate.unobtrusive.min.js"></script>
<script src="~/js/date-range-validation.js"></script>
Testing:

Now, run the application and try to enter an invalid date range, and then you should get the error message as shown in the below image:

Custom Data Annotation Real-Time Examples in ASP.NET Core MVC

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

Leave a Reply

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