Model validation is an important feature in ASP.NET Core MVC that helps us verify that the data entered by users matches expectations.

Validation with data annotations

See the Microsoft data annotations documentation.

public class User
{
    [Required]
    [StringLength(32)]
    public string Name { get; set; }

    [Required]
    [EmailAddress]
    public string Email { get; set; }
}

Validation with fluent chaining

FluentValidation is a .NET library for building type-safe validation rules. Its design goal is to provide a simple, clear API while still supporting complex validation rules.

See the official FluentValidation documentation.

dotnet add package FluentValidation.AspNetCore

Implementing a validator

public class UserValidator : AbstractValidator<User>
{
    public UserValidator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(32);
        RuleFor(x => x.Email).NotEmpty().EmailAddress();
    }
}

Automatic dependency injection

services.AddValidatorsFromAssembly(assembly).AddFluentValidationAutoValidation();

ValidatorOptions.Global.LanguageManager = new CustomFluentValidationLanguageManager();

Custom validation error messages

public class UserValidator : AbstractValidator<User>
{
    public UserValidator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(32).WithMessage("Name is required and must be less than 32 characters.");
        RuleFor(x => x.Email).NotEmpty().EmailAddress().WithMessage("Email is required and must be a valid email address.");
    }
}

Custom validation logic

public class UserCreateRequestValidator : AbstractValidator<UserCreateRequest>
{
    public UserCreateRequestValidator(IdentityDbContext context)
    {
        RuleFor(m => m.UserName).NotNull().NotEmpty().Length(5, 20).Matches("^[a-zA-Z]+$");
        RuleFor(m => m.PhoneNumber).NotNull().NotEmpty().Length(11).Matches(@"^1\d{10}$").Must((model, phoneNumber) =>
        {
            return !context.Users.Any(e => e.PhoneNumber == phoneNumber);
        });
        RuleFor(m => m.Password).NotNull().NotEmpty().Length(5, 20);
        RuleFor(m => m.Email).EmailAddress().Length(5, 50);
    }
}

Common error messages

public class CustomFluentValidationLanguageManager : FluentValidation.Resources.LanguageManager
{
    public CustomFluentValidationLanguageManager()
    {
        AddTranslation("en", "NotNullValidator", "The {PropertyName} field is required.");
        AddTranslation("en", "MaximumLengthValidator", "The {PropertyName} field must be less than {MaxLength} characters.");
        AddTranslation("en", "EmailAddressValidator", "The {PropertyName} field must be a valid email address.");
    }
}