-
Custom Validation in ASP.NET Core MVC
-
In real-world applications, validation requirements often go beyond simple rules like required fields or email formats. While Data Annotations provide a strong foundation for validation, they are limited to predefined rules.
There are many scenarios where built-in annotations are not sufficient. For example, validating business rules, comparing multiple fields, or applying conditional logic.
This is where Custom Validation becomes essential in ASP.NET Core MVC.
Custom Validation allows developers to define their own validation logic and extend the validation system according to application requirements.
What is Custom Validation?
Custom Validation is the process of creating custom rules to validate model data when built-in Data Annotations are not enough.
It allows developers to define validation logic based on business requirements.
When Do You Need Custom Validation?
- When validation depends on multiple fields
- When business rules are involved
- When conditional validation is required
- When built-in annotations are not sufficient
Custom validation ensures flexibility in handling complex scenarios.
Approach 1: Custom Validation Attribute
The most common way to implement custom validation is by creating a custom attribute.
This is done by inheriting from ValidationAttribute.
Step 1: Create Custom Attribute
using System.ComponentModel.DataAnnotations; public class AgeValidationAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext context) { int age = (int)value; if (age < 18) { return new ValidationResult("Age must be greater than 18"); } return ValidationResult.Success; } }
Step 2: Use Custom Attribute
public class User { [AgeValidation] public int Age { get; set; } }
This applies the custom validation rule to the Age property.
How It Works
- Model Binding creates the object
- Validation system calls IsValid()
- If condition fails → error message is returned
Approach 2: IValidatableObject
When validation depends on multiple properties, custom attributes are not always sufficient.
In such cases, IValidatableObject is used.
Example
using System.ComponentModel.DataAnnotations; using System.Collections.Generic; public class User : IValidatableObject { public string Password { get; set; } public string ConfirmPassword { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext context) { if (Password != ConfirmPassword) { yield return new ValidationResult( "Passwords do not match", new[] { "ConfirmPassword" }); } } }
What Happens Here?
- Validation runs after model binding
- Validate() method is executed
- Errors are returned if conditions fail
Real-World Scenario
Consider a registration system where:
- User must be above 18 years
- Password and Confirm Password must match
These rules cannot be handled fully using built-in annotations.
Custom Validation ensures these business rules are enforced before processing data.
Displaying Errors in UI
Custom validation errors are displayed in the same way as built-in validation errors.
<span> @Html.ValidationMessage("Age") </span>
Important: These messages appear only after the form is submitted. This is server-side validation.
Custom Validation: Trimmed Required Field (Practical Utility)
In real applications, users often enter values that contain only spaces. Standard validation like [Required] does not handle this scenario effectively.
To solve this, we can create a custom validation attribute that automatically trims input and checks if the value is truly empty.
Step 1: Create Custom Attribute
using System.ComponentModel.DataAnnotations; public class TrimmedRequiredAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext context) { if (value == null) { return new ValidationResult("Field is required"); } var input = value.ToString().Trim(); if (string.IsNullOrEmpty(input)) { return new ValidationResult("Field cannot be empty or spaces"); } return ValidationResult.Success; } }
Step 2: Use in Model
public class User { [TrimmedRequired] public string Name { get; set; } }
What Makes This Useful?
- Prevents empty space inputs
- Automatically trims user input
- Reusable across multiple fields
- Improves data quality without extra controller logic
Real Benefit
Instead of writing manual checks in controllers like:
if (model.Name.Trim() == "")
You can simply use:
[TrimmedRequired]This keeps your controller clean and moves validation logic to the model where it belongs.
Good validation is not just about rules — it is about improving data quality and reducing repetitive code.
Custom Validation: Password Strength Validator
In many applications, passwords must meet specific security rules such as minimum length, uppercase letters, numbers, and special characters.
Step 1: Create Attribute
using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; public class StrongPasswordAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext context) { var password = value?.ToString(); if (password == null) return new ValidationResult("Password is required"); if (!Regex.IsMatch(password, @"^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{6,}$")) { return new ValidationResult( "Password must contain uppercase, number, and special character"); } return ValidationResult.Success; } }
Use in Model
[StrongPassword] public string Password { get; set; }
Custom Validation: No Future Date
In scenarios like date of birth or past records, future dates should not be allowed.
Create Attribute
public class NoFutureDateAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext context) { if (value == null) return ValidationResult.Success; var date = (DateTime)value; if (date > DateTime.Now) { return new ValidationResult("Future date is not allowed"); } return ValidationResult.Success; } }
Use in Model
[NoFutureDate] public DateTime DateOfBirth { get; set; }
Custom Validation: Unique Email (Database Check)
In real applications, email addresses must be unique. This requires checking the database.
Create Attribute
public class UniqueEmailAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext context) { var email = value?.ToString(); var db = (AppDbContext)context.GetService(typeof(AppDbContext)); var exists = db.Users.Any(u => u.Email == email); if (exists) { return new ValidationResult("Email already exists"); } return ValidationResult.Success; } }
Use in Model
[UniqueEmail] public string Email { get; set; }
UI Behavior: Validation on Submit
The following UI shows how validation errors appear after the user clicks the Submit button.
Razor Form
<form method="post"> <input name="Password" /> <span>@Html.ValidationMessage("Password")</span> <input name="DateOfBirth" /> <span>@Html.ValidationMessage("DateOfBirth")</span> <input name="Email" /> <span>@Html.ValidationMessage("Email")</span> <button type="submit"> Submit </button> </form>
UI Output
Password must contain uppercase, number, and special character
Future date is not allowed
Email already exists
Important: These errors appear only after clicking the Submit button. This is server-side validation.
Advantages of Custom Validation
- Supports complex business rules
- Flexible and extendable
- Integrates with MVC validation pipeline
Summary
Custom Validation in ASP.NET Core MVC allows developers to define advanced validation rules beyond built-in Data Annotations. It is essential for handling real-world scenarios where validation depends on business logic or multiple fields.
By creating custom attributes or implementing IValidatableObject, developers can extend the validation system and ensure that data meets application-specific requirements.
Custom Validation integrates seamlessly with Model Binding and Model Validation, making it a powerful tool for building robust applications.
Understanding Custom Validation is key to handling complex data validation scenarios effectively.