Asp.Net Core Minimal APIs with FluentValidation

In this post we will use how the model can be validated in minimal APIs and how FluentValidation can help us in this process.

What is Asp.Net Core Minimal APIs

Minimal APIs are a new feature in .NET 6 that helps the developers to create APIs with minimal dependencies in ASP.NET Core. Minimal APIs simplify API development through the use of a more compact code syntax.

Validation

In Web API development it is very important to validate the request before processing it. A lot of validations can be done and one of these validations is the Model.

Model Validation is a technique used to validate the model state before continuing with the request, checking if the data meets all the defined rules.

Minimal APIs do not come with any built-in support for validation, like Data Annotations, however we are free to create our own validation or use libraries like MinimalValidation and FluentValidation.

FluentValidation

FluentValidation is a .NET library for building strongly-typed validation rules. It is an open source and uses a fluent interface and lambda expressions for creating validation rules.

FluentValidation has several built-in validators. The error message for each validator can contain special placeholders that will be filled in when the error message is constructed.

Some examples of validators:

RuleFor(customer => customer.Surname).NotNull();

RuleFor(customer => customer.Surname).NotEmpty();

//Not equal to a particular value
RuleFor(customer => customer.Surname).NotEqual("Foo");

//Equal to a particular value
RuleFor(customer => customer.Surname).Equal("Foo");

//Equal to another property
RuleFor(customer => customer.Password).Equal(customer => customer.PasswordConfirmation);

//must be between 1 and 250 chars (inclusive)
RuleFor(customer => customer.Surname).Length(1, 250); 

//must be 10 chars or more
RuleFor(customer => customer.Surname).MinimumLength(10); 

//Less than a particular value
RuleFor(customer => customer.CreditLimit).LessThan(100);

//Less than another property
RuleFor(customer => customer.CreditLimit).LessThan(customer => customer.MaxCreditLimit);

The complete list of validators can be found in here.

Let's code

First we will create our ASP.NET Core Web API. For Minimal API's we need to uncheck the option Use controllers.

MinimalAPIs.png

The default code shows an implementation of one endpoint, weatherforecast, in the Program.cs file but we will create different endpoints using a different model.

Model

This model will be used to create the context service, validator and the endpoints.

public class Todo
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
    public DateTimeOffset? CompletedTimestamp { get; set; }
}

Context

To persist the data a DbContext implementation will be created and for this the nuget package Microsoft.EntityFrameworkCore needs to be installed.

public class TodoDb : DbContext
{
    public TodoDb(DbContextOptions<TodoDb> options)
        : base(options) { }

    public DbSet<Todo> Todos => Set<Todo>();
}

Validator

To create the validator is necessary to install the nuget package FluentValidation.AspNetCore.

With this library is possible to define the rules for each property of the class used in the request of the endpoint, can be the model or a DTO, for this post it will be created for the model.

public class TodoValidator : AbstractValidator<Todo>
{
    public TodoValidator()
    {
        RuleFor(m => m.Name).NotEmpty().WithMessage("The field 'Name' is required.");
        RuleFor(m => m.IsComplete).NotEmpty();
        RuleFor(m => m.CompletedTimestamp).LessThanOrEqualTo(DateTime.Now);
    }
}

The validator has rules for three properties: Name, IsComplete and CompletedTimestamp. Name and IsComplete are required and the Name has a custom message. For the property CompletedTimestamp should be less than or equal to the current date.

Settings

Before using the validator and the dbContext it is necessary to add them in the services of the services collection of the API.

builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddScoped<IValidator<Todo>, TodoValidator>();

Endpoints

The validator and the dbContext will be injected to the endpoint and the model can be validated before being persisted.

app.MapPost("/todoitems", async (IValidator<Todo> validator, Todo todo, TodoDb db) =>
{
    ValidationResult validationResult = await validator.ValidateAsync(todo);

    if (!validationResult.IsValid)
    {
        return Results.ValidationProblem(validationResult.ToDictionary());
    }

    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

Results

The property CompletedTimestamp is not required so it will be validated only when it has a value.

Validation00.png

validation001.png

The properties Name and IsComplete are required, so they need to have value to continue. For the property Name the message was customized.

Validation01.png

Wrapping Up

You can find the full code on my GitHub.