← Back to Articles         
Data Annotations & Custom Validation

Data Annotations & Custom Validation

Published on 27 Sep 2026     4 min read ASP.NET Core MVC
MVC

Data Annotations and Custom Validation Attributes

Both are used to define validation rules for model properties.

1. Data Annotations

Data Annotations are built-in validation attributes placed on model properties.

public class EmployeeViewModel
{
    [Required(ErrorMessage = "Name is required.")]
    [StringLength(50, MinimumLength = 3)]
    public string Name { get; set; } = string.Empty;

    [EmailAddress]
    public string Email { get; set; } = string.Empty;

    [Range(18, 60)]
    public int Age { get; set; }
}

Common Data Annotations

Attribute Purpose
[Required] Value is mandatory
[StringLength] Controls maximum and minimum length
[Range] Validates numeric range
[EmailAddress] Validates email format
[Phone] Validates phone format
[Compare] Compares two properties
[RegularExpression] Validates using a pattern

2. Custom Validation Attribute

Create a custom attribute when built-in annotations cannot handle a specific rule.

Example: Employee joining date cannot be in the future.

using System.ComponentModel.DataAnnotations;

public class NotFutureDateAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(
        object? value,
        ValidationContext validationContext)
    {
        if (value is DateTime date && date > DateTime.Today)
        {
            return new ValidationResult(
                ErrorMessage ??
                "Date cannot be in the future.");
        }

        return ValidationResult.Success;
    }
}

Apply the Custom Attribute

public class EmployeeViewModel
{
    [Required]
    public string Name { get; set; } = string.Empty;

    [NotFutureDate(
        ErrorMessage = "Joining date cannot be in the future.")]
    public DateTime JoiningDate { get; set; }
}

How Validation Runs

Form submission
      ↓
Model binding
      ↓
Built-in and custom attributes execute
      ↓
Errors are stored in ModelState
      ↓
ModelState.IsValid is checked
[HttpPost]
public IActionResult Create(EmployeeViewModel model)
{
    if (!ModelState.IsValid)
        return View(model);

    return RedirectToAction("Index");
}

Data Annotations vs Custom Attributes

Data Annotations Custom validation attributes
Built into .NET Created by the developer
Used for common rules Used for application-specific rules
Less code Requires a class derived from ValidationAttribute
Commonly supports client validation Custom client validation needs additional implementation

Key Points

  • Data Annotations handle common validation rules.
  • Custom validation attributes handle reusable, specialized rules.
  • A custom attribute derives from ValidationAttribute.
  • Override IsValid() to implement the validation rule.
  • Validation errors are added to ModelState.
  • Complex business rules should usually remain in the domain or service layer, not only in validation attributes.

When Would You Use FluentValidation?

Use FluentValidation when validation rules become complex and Data Annotations make the model difficult to maintain.

Suitable Scenarios

  • Complex validation rules
  • Conditional validation
  • Validation involving multiple properties
  • Reusable validation rules
  • Separate validation for create and update operations
  • Validators requiring dependency injection
  • Keeping validation outside ViewModels or DTOs
  • Unit-testing validation rules independently

Example

public class EmployeeValidator
    : AbstractValidator<EmployeeViewModel>
{
    public EmployeeValidator()
    {
        RuleFor(x => x.Name)
            .NotEmpty()
            .MaximumLength(50);

        RuleFor(x => x.Email)
            .NotEmpty()
            .EmailAddress();

        RuleFor(x => x.Age)
            .InclusiveBetween(18, 60);

        RuleFor(x => x.ManagerName)
            .NotEmpty()
            .When(x => x.IsManager);
    }
}

Data Annotations vs FluentValidation

Data Annotations FluentValidation
Built into .NET Third-party library
Attributes placed on properties Rules stored in separate validator classes
Best for simple validation Best for complex validation
Limited conditional rules Supports conditional and cross-property rules
Can clutter large models Keeps models clean
Less flexible for testing Validators are easy to unit test
Good for small applications Useful for large, layered applications

When Not to Use It

For a small form with basic rules, Data Annotations are usually sufficient:

[Required]
[StringLength(50)]
public string Name { get; set; }

Key Points

  • Use FluentValidation when validation is complex, conditional or reusable.
  • It keeps validation separate from the model.
  • It supports clear, strongly typed and testable rules.
  • It is useful in Clean Architecture because validators can live in the Application layer.
  • Do not put core business rules only in FluentValidation; enforce critical business rules in the domain or service layer as well.

Yes, FluentValidation Requires a NuGet Package

For ASP.NET Core MVC, install these packages:

dotnet add package FluentValidation
dotnet add package FluentValidation.DependencyInjectionExtensions

The second package provides dependency-injection registration such as AddValidatorsFromAssemblyContaining().

Register Validators in Program.cs

using FluentValidation;

builder.Services.AddValidatorsFromAssemblyContaining<
    EmployeeValidator>();

Inject and Use the Validator

public class EmployeeController : Controller
{
    private readonly IValidator<EmployeeViewModel> _validator;

    public EmployeeController(
        IValidator<EmployeeViewModel> validator)
    {
        _validator = validator;
    }

    [HttpPost]
    public async Task<IActionResult> Create(
        EmployeeViewModel model)
    {
        var result = await _validator.ValidateAsync(model);

        if (!result.IsValid)
        {
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError(
                    error.PropertyName,
                    error.ErrorMessage);
            }

            return View(model);
        }

        return RedirectToAction("Index");
    }
}

Important Note

Avoid starting new projects with:

dotnet add package FluentValidation.AspNetCore

That automatic MVC integration package is deprecated and no longer maintained. Prefer manual asynchronous validation using IValidator<T>. The current core and DI packages are listed on NuGet: FluentValidation and FluentValidation.DependencyInjectionExtensions.

 

 

← Back to Articles