Asp.Net Core Using Fluentvalidation Complete Guide
Understanding the Core Concepts of ASP.NET Core Using FluentValidation
ASP.NET Core Using FluentValidation: A Comprehensive Guide
1. Installation
The first step is to install FluentValidation via NuGet. To do this using the NuGet Package Manager Console:
Install-Package FluentValidation.AspNetCore
Alternatively, you can add the package through Visual Studio’s NuGet package manager by searching for "FluentValidation" and installing it.
2. Basic Setup
To integrate FluentValidation into an ASP.NET Core project, you need to configure it within the Startup.cs
file (or Program.cs
as per newer ASP.NET Core versions). Here’s how you can set it up:
For ASP.NET Core 3.0 and later where DI setup is done in Program.cs
:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServices((context, services) =>
{
services.AddControllers(options =>
options.Filters.Add(typeof(ValidateModelAttribute))
)
.AddFluentValidation(options =>
{
// Automatic registration of validators from assemblies.
options.RegisterValidatorsFromAssemblyContaining(typeof(MyFirstValidator));
options.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
});
});
}
For older versions like ASP.NET Core 2.x, the setup is done in Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<MyFirstValidator>());
// Ensure default MVC validation is not run after FluentValidation executes.
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
}
3. Creating Validators
A validator is a class that defines the validation rules. Suppose you have a Person
model:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
You create a corresponding validator inheriting from AbstractValidator<T>
:
using FluentValidation;
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(person => person.Name)
.NotEmpty()
.WithMessage("Name cannot be empty.")
.MaximumLength(255);
RuleFor(person => person.Age)
.InclusiveBetween(18, 99);
}
}
4. Automatically Validating Models
ASP.NET Core automatically applies the created validators to model instances upon binding if you've configured FluentValidation properly in the Startup.cs
, as shown above. Here’s an example controller action:
[ApiController]
[Route("[controller]")]
public class PeopleController : ControllerBase
{
[HttpPost]
public IActionResult Create(Person person)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Proceed with the creation logic...
return Ok();
}
}
In this controller action, the ModelState.IsValid
check will invoke the PersonValidator
before proceeding any further.
5. Validation Rules
FluentValidation supports a wide range of validation rules including standard ones such as NotEmpty()
, Length()
, Matches()
, and custom ones. Below are a few examples demonstrating more advanced rules:
- Custom Rules with Predicate:
RuleFor(x => x.Email)
.EmailAddress()
.When(x => !string.IsNullOrEmpty(x.Email));
- Conditional Rules:
RuleFor(p => p.Company)
.NotEqual("Unknown")
.OnlyWhen(p => p.IsEmployed);
- Custom Validator:
Sometimes built-in validators aren't sufficient; you may need a custom validator:
RuleFor(x => x.Password)
.Must(HaveRequiredPasswordCharacteristics)
.WithMessage("Password does not meet required characteristics.");
private bool HaveRequiredPasswordCharacteristics(string password)
{
return password.Any(char.IsUpper) && password.Any(char.IsLower) && password.Any(char.IsDigit);
}
6. Handling Validation Results
When validation fails, ModelState
stores error messages that can be returned to the client in an IActionResult
. This can be leveraged in multiple ways depending on the type of application.
- Returning Validation Errors:
public IActionResult Create(Person person)
{
if (!ModelState.IsValid)
{
return ValidationProblem(ModelState);
}
// If valid, process the request...
return CreatedAtAction(nameof(GetPerson), new { id = person.Id }, person);
}
7. Custom Error Messages and Resources
It’s common to use localizable resources to define custom error messages:
Using resource files:
RuleFor(x => x.Email)
.NotEmpty()
.WithLocalizedMessage(typeof(ValidationResources), "EmailEmpty");
Where ValidationResources.resx
contains the key-value pair "EmailEmpty: Email cannot be empty."
8. Applying Validators Globally
Instead of validating models individually per Controller Action, you can enforce validations globally:
services.AddMvc(setupAction =>
{
// Add the ValidateModelAttribute filter globally
setupAction.Filters.Add(typeof(ValidateModelAttribute));
});
// Define the ValidateModelAttribute to utilize FluentValidation
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
9. Custom Property Names
Sometimes displaying exact property names in error messages isn't desirable due to security or readability reasons. Custom display names for properties can be specified:
public class MyModel
{
public string Username { get; set; }
}
public class MyModelValidator : AbstractValidator<MyModel>
{
public MyModelValidator()
{
RuleFor(x => x.Username)
.NotEmpty()
.WithMessage("{PropertyName} is required.")
.DisplayName("User name");
}
}
Here, {PropertyName}
will be replaced with "User name".
10. Validator Composition
Complex validation can often be simplified by splitting them into multiple validators which can then be composed together:
// Child Validator
public class ChildValidator : AbstractValidator<Address>
{
public ChildValidator()
{
RuleFor(address => address.StreetName)
.NotEmpty();
RuleFor(address => address.BuildingNumber)
.GreaterThan(0);
}
}
// Parent Validator
public class ParentValidator : AbstractValidator<Person>
{
public ParentValidator()
{
RuleFor(p => p.HomeAddress)
.SetValidator(new ChildValidator());
RuleFor(p => p.Name)
.NotEmpty();
}
}
11. Validation in Services Layer
While FluentValidation is primarily used with controllers, you can also perform validations in service layers:
public class PersonService
{
private readonly IValidator<Person> _validator;
public PersonService(IValidator<Person> validator)
{
_validator = validator;
}
public async Task<Person> AddAsync(Person person)
{
var result = await _validator.ValidateAsync(person);
if (!result.IsValid)
{
throw new ValidationException(result.Errors);
}
// Save to database, etc.
return person;
}
}
This approach ensures consistent validation across different parts of your application.
12. Advanced Customization
FluentValidation offers extensive customization capabilities:
- Global Configuration:
Online Code run
Step-by-Step Guide: How to Implement ASP.NET Core Using FluentValidation
Step 1: Create a New ASP.NET Core MVC Project
- Open Visual Studio and create a new project.
- Choose ASP.NET Core Web App (Model-View-Controller).
- Name your project
FluentValidationExample
and click Create. - Select the target framework (e.g., .NET 6.0) and click Create.
Step 2: Install FluentValidation NuGet Package
To use FluentValidation in your project, you need to install the FluentValidation.AspNetCore
package.
- Right-click on your project in Solution Explorer and select Manage NuGet Packages.
- Go to the Browse tab and search for
FluentValidation.AspNetCore
. - Click on the package and then click Install.
Step 3: Define a Model
Create a model class that you want to validate. For example, let's create a Person
model.
namespace FluentValidationExample.Models
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
}
Step 4: Create a Validator for the Model
Now, create a validator class for the Person
model using FluentValidation.
using FluentValidation;
namespace FluentValidationExample.Validators
{
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(p => p.FirstName)
.NotEmpty().WithMessage("First name is required.")
.MaximumLength(50).WithMessage("First name cannot exceed 50 characters.");
RuleFor(p => p.LastName)
.NotEmpty().WithMessage("Last name is required.")
.MaximumLength(50).WithMessage("Last name cannot exceed 50 characters.");
RuleFor(p => p.Age)
.InclusiveBetween(18, 120).WithMessage("Age must be between 18 and 120.");
}
}
}
Step 5: Configure FluentValidation in Startup.cs
(or Program.cs
in .NET 6+)
In .NET 6+, you configure services in the Program.cs
file. Register the validator with the Services collection.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
// Register FluentValidation and the validator
builder.Services.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<PersonValidator>());
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Step 6: Create Controller and Views
Create a controller and views to handle displaying and submitting the Person
model.
Controller: PeopleController.cs
Top 10 Interview Questions & Answers on ASP.NET Core Using FluentValidation
1. What is FluentValidation and why should you use it in ASP.NET Core?
Answer:
FluentValidation is a lightweight, unobtrusive, and highly extensible library designed for building strongly-typed validation logic in .NET applications, including those built with ASP.NET Core. It allows developers to define complex validation rules in a fluent API manner, making the validation process more maintainable and easier to understand than traditional data annotations.
2. How do you install FluentValidation in an ASP.NET Core project?
Answer:
You can install FluentValidation via NuGet Package Manager. Use one of the following commands:
- Visual Studio: Right-click on your project → Manage NuGet Packages → Search for “FluentValidation.AspNetCore” → Install.
- Powershell Command Line:
Install-Package FluentValidation.AspNetCore
. - Dotnet CLI:
dotnet add package FluentValidation.AspNetCore
.
Once installed, you need to integrate it into your application's startup configuration.
3. How do you set up FluentValidation in your ASP.NET Core application?
Answer:
To set up FluentValidation in an ASP.NET Core app, follow these steps:
- Create a Validator class by inheriting from
AbstractValidator<T>
.public class PersonValidator : AbstractValidator<Person> { public PersonValidator() { RuleFor(p => p.Name).NotEmpty().WithMessage("Name cannot be empty"); RuleFor(p => p.Email).NotEmpty().EmailAddress(); } }
- Register the validator in the
Startup.cs
orProgram.cs
(for .NET 6 and later) file within theConfigureServices
method.services.AddFluentValidation(fv => { fv.RegisterValidatorsFromAssemblyContaining<PersonValidator>(); });
- Ensure that model binding uses DataAnnotations and FluentValidation validators. ASP.NET Core uses both by default after integrating FluentValidation.
4. Can you validate nested objects in FluentValidation?
Answer:
Yes, validating nested objects is straightforward in FluentValidation. You can use the SetValidator
method to apply another validator to a child object.
public class AddressValidator : AbstractValidator<Address>
{
public AddressValidator()
{
RuleFor(address => address.City).NotEmpty();
RuleFor(address => address.Postcode).Length(6, 8);
}
}
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(p => p.Name).NotEmpty();
RuleFor(p => p.Email).NotEmpty().EmailAddress();
RuleFor(p => p.Address).SetValidator(new AddressValidator());
}
}
5. How do you handle custom validation rules in FluentValidation?
Answer:
FluentValidation supports custom validation methods through Custom()
or Must()
. Here’s an example using CustomAsync()
for async operations:
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(person => person.Age)
.GreaterThan(0)
.CustomAsync(async (age, context, cancellationToken) =>
{
var db = context.GetService<MyDbContext>();
var isValidAge = await IsValidAge(age);
if (!isValidAge)
{
context.AddFailure("Invalid age");
}
});
}
private async Task<bool> IsValidAge(int age)
{
// some async operation to check the age
return await Task.FromResult(age > 18);
}
}
6. How can you specify different rule sets for different validation scenarios?
Answer:
You can create multiple named rule sets within your validator class and then select which one to use at runtime.
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleSet("Create", () =>
{
RuleFor(p => p.Name).NotEmpty();
});
RuleSet("Update", () =>
{
RuleFor(p => p.Email).NotEmpty().EmailAddress();
});
}
}
In your controller, specify the rule set:
public IActionResult Save(Person person)
{
var validationResult = _validator.Validate(person, options =>
{
options.IncludeRuleSets("Create");
});
if (!validationResult.IsValid)
{
foreach (var failure in validationResult.Errors)
{
ModelState.AddModelError(failure.PropertyName, failure.ErrorMessage);
}
return BadRequest(ModelState);
}
// save logic here
return Ok();
}
7. How do you display validation errors in a Razor page or View?
Answer:
ASP automatically binds validation errors to the model, making them available in Razor views.
@using FluentValidation.AspNetCore
<form method="post">
<input type="text" asp-for="Person.Name" />
<span asp-validation-for="Person.Name"></span>
<button type="submit">Submit</button>
</form>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
In your action method, FluentValidation's results are added to ModelState
which you should check before proceeding.
[HttpPost]
public IActionResult AddPerson(Person person)
{
var validationResult = _personValidator.Validate(person);
if (!validationResult.IsValid)
{
validationResult.AddToModelState(ModelState);
return View(modelState: ModelState);
}
// add to database etc.
return RedirectToAction(nameof(Index));
}
8. What is the difference between Data Annotations and FluentValidation?
Answer:
Data Annotations: These are attributes added directly to the model classes. They are easy to use but can clutter the model with validation logic and make it harder to maintain, especially for complex validation rules.
FluentValidation: Separates validation rules entirely from your model classes into dedicated validator classes. This enhances modularity, readability, and maintainability, allowing complex validation logic to be constructed through a fluent API.
9. How do you perform validation manually in FluentValidation outside of ASP.NET Core’s MVC system?
Answer:
You can instantiate and use validators manually anywhere within your application, not just in MVC controllers.
public class PersonService
{
private readonly IValidator<Person> _validator;
public PersonService(IValidator<Person> validator)
{
_validator = validator;
}
public async Task<string> CreatePersonAsync(Person person)
{
var validationResult = await _validator.ValidateAsync(person);
if (!validationResult.IsValid)
{
// Handle invalid state
return string.Join(", ", validationResult.Errors.Select(e => e.ErrorMessage));
}
// Proceed with creating person
return "Person Created successfully!";
}
}
10. How do you extend FluentValidation to include global validation behaviors like logging all validation failures?
Answer:
You can extend FluentValidation by implementing a custom IValidatorInterceptor
. This interceptor can add pre- or post-validation behaviors, such as logging.
public class LoggingValidatorInterceptor : IValidatorInterceptor
{
private readonly ILogger<LoggingValidatorInterceptor> _logger;
public LoggingValidatorInterceptor(ILogger<LoggingValidatorInterceptor> logger)
{
_logger = logger;
}
public IValidationContext BeforeValidation(IValidationContext context)
{
// Log before validation
_logger.LogInformation($"Validating {context.InstanceToValidate}");
return context;
}
public ValidationResult AfterValidation(IValidationContext context, ValidationResult result)
{
// Log all validation errors
if (!result.IsValid)
{
var errors = result.Errors.Select(e => $"{e.PropertyName}: {e.ErrorMessage}");
_logger.LogWarning($"Validation failed for {string.Join("; ", errors)}");
}
return result;
}
}
Register the interceptor in Startup.cs
or Program.cs
:
Login to post a comment.