ASP.NET MVC
Custom Validation
DataAnnotations
Web Development
C#

ASP.NET MVC Custom Validation by DataAnnotation

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

ASP.NET MVC supports validation through DataAnnotations, which lets you place validation rules directly on model properties. Built-in attributes cover many common cases, but custom validation becomes necessary when the rule is domain-specific. The usual way to do that is to create your own attribute by inheriting from ValidationAttribute.

Create a Custom Validation Attribute

A custom DataAnnotation starts by subclassing ValidationAttribute and overriding IsValid.

csharp
1using System;
2using System.ComponentModel.DataAnnotations;
3
4public class MinimumAgeAttribute : ValidationAttribute
5{
6    private readonly int _minimumAge;
7
8    public MinimumAgeAttribute(int minimumAge)
9    {
10        _minimumAge = minimumAge;
11    }
12
13    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
14    {
15        if (value == null)
16        {
17            return ValidationResult.Success;
18        }
19
20        if (value is DateTime birthDate)
21        {
22            var today = DateTime.Today;
23            var age = today.Year - birthDate.Year;
24
25            if (birthDate.Date > today.AddYears(-age))
26            {
27                age--;
28            }
29
30            if (age >= _minimumAge)
31            {
32                return ValidationResult.Success;
33            }
34        }
35
36        return new ValidationResult(ErrorMessage ?? $"Age must be at least {_minimumAge}.");
37    }
38}

This attribute checks whether the given birth date satisfies the minimum age rule.

Apply the Attribute to a Model

Once the attribute exists, use it like any built-in DataAnnotation:

csharp
1using System;
2using System.ComponentModel.DataAnnotations;
3
4public class RegistrationViewModel
5{
6    [Required]
7    public string Name { get; set; }
8
9    [MinimumAge(18, ErrorMessage = "You must be at least 18 years old.")]
10    public DateTime? BirthDate { get; set; }
11}

When model binding runs, MVC evaluates the attribute and adds validation errors to ModelState if the rule fails.

Check ModelState in the Controller

Custom attributes participate in the normal validation flow:

csharp
1[HttpPost]
2public ActionResult Register(RegistrationViewModel model)
3{
4    if (!ModelState.IsValid)
5    {
6        return View(model);
7    }
8
9    return RedirectToAction("Success");
10}

This is why custom DataAnnotations feel natural in MVC. They integrate with the same validation pipeline as Required, Range, and the other built-in attributes.

When Property-Level Validation Is Not Enough

Some rules depend on multiple properties rather than one field in isolation. For example, one date may need to be after another. In those cases, IValidatableObject is often a better fit than a property-level custom attribute.

csharp
1using System.Collections.Generic;
2using System.ComponentModel.DataAnnotations;
3
4public class BookingViewModel : IValidatableObject
5{
6    public DateTime StartDate { get; set; }
7    public DateTime EndDate { get; set; }
8
9    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
10    {
11        if (EndDate < StartDate)
12        {
13            yield return new ValidationResult(
14                "End date must be after start date.",
15                new[] { nameof(EndDate) }
16            );
17        }
18    }
19}

This is often cleaner than forcing cross-field logic into a single-property attribute.

Server Validation Versus Client Validation

A basic custom ValidationAttribute gives you server-side validation automatically. Client-side validation is a separate concern. In classic ASP.NET MVC, extra work is required if you want the browser to enforce the same custom rule through unobtrusive validation.

That does not make the custom attribute incomplete. It just means you should be clear about where the rule is being enforced.

Common Pitfalls

One common mistake is putting cross-property logic into a property-level attribute when IValidatableObject would express the rule more cleanly.

Another mistake is assuming that a custom ValidationAttribute automatically produces client-side validation behavior. Server-side validation comes first; client-side support may require additional wiring.

Developers also sometimes return false or throw exceptions instead of returning a proper ValidationResult. A clear validation result makes the MVC pipeline behave predictably.

Finally, remember that null handling is a design choice. If the field must be present, combine your custom attribute with Required instead of making the custom attribute responsible for every possible rule.

Summary

  • Custom MVC DataAnnotation validation starts by inheriting from ValidationAttribute.
  • Apply the custom attribute to model properties just like built-in validation attributes.
  • MVC adds validation failures to ModelState automatically during model binding.
  • Use IValidatableObject when the rule depends on multiple properties.
  • Server-side validation is automatic; client-side custom validation usually needs extra setup.

Course illustration
Course illustration

All Rights Reserved.