Kimmel.FluentValidationEndpointFilter 1.0.0

dotnet add package Kimmel.FluentValidationEndpointFilter --version 1.0.0
                    
NuGet\Install-Package Kimmel.FluentValidationEndpointFilter -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Kimmel.FluentValidationEndpointFilter" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Kimmel.FluentValidationEndpointFilter" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Kimmel.FluentValidationEndpointFilter" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Kimmel.FluentValidationEndpointFilter --version 1.0.0
                    
#r "nuget: Kimmel.FluentValidationEndpointFilter, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Kimmel.FluentValidationEndpointFilter@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Kimmel.FluentValidationEndpointFilter&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Kimmel.FluentValidationEndpointFilter&version=1.0.0
                    
Install as a Cake Tool

Fluent Validation Endpoint Filter

Backstory

We often find ourselves needing to perform validation on the models being submitted to our Minimal APIs. Traditionally, we would have something like the following:

app.MapPost(string.Empty, async (AppDbContext db, BrandCreateModel model, IValidator<BrandCreateModel> validator) => {
   var valResult = await validator.ValidateAsync(model);
   if (!valResult.IsValid) {
     return Results.ValidationProblem(result.ToDictionary(), statusCode: StatusCodes.Status422UnprocessableEntity);
   }
   var brand = new Brand() { Name = model.Name, Description = model.Description };
   db.Brands.Add(brand);
   await db.SaveChangesAsync();
   return Results.Created($"/api/brands/{brand.Id}", brand);
})
  .Produces(StatusCodes.Status422UnprocessableEntity)
  .Produces<Brand>(StatusCodes.Status201Created);

There isn't anything wrong with this. It is functional and understandable. However, repeating the validation logic in every POST and PUT method felt very repetitive. We figured there had to be a layer we could take advantage of to reduce this duplication. What we eventually discovered was the IEndpointFilter interface.

IEndpointFilter allows you to create filters that run prior to the body of your Minimal API methods. Sound like a great place to do validation, right?!

After some Googling, we discovered this wasn't a new problem. Ben Foster came up with a solution in 2022 for .Net 7 minimal APIs. We've studied that solution, made "improvements" utilizing features available in .Net 9, and are publishing a NuGet package making it easy for others to implement. If your org doesn't like 3rd-party NuGets, or you don't like how something works, the source is right here.

Usage

Usage is straight-forward. Create your models and validators, then register them with Dependency Injection like you normally would. Then it is just two simple steps...

  1. Download the NuGet package
  2. Add .Validate<ModelType>() to the end of your API method you would like to provide validation for

Here is the simplified version of the API definition at the beginning of this document:

app.MapPost(string.Empty, async (AppDbContext db, BrandCreateModel model) => {
   var brand = new Brand() { Name = model.Name, Description = model.Description };
   db.Brands.Add(brand);
   await db.SaveChangesAsync();
   return Results.Created($"/api/brands/{brand.Id}", brand);
})
  .Validate<BrandCreateModel>()
  .Produces<Brand>(StatusCodes.Status201Created);

It is important to note that Validate<BrandCreateModel>() not only performs the validation, but it also checks to make sure the model is present, and sets the possible Open API return types. For instance, if the body is missing from the request and the model ends up being null, it produces a BadRequest<string> with the default message "Request body is missing and cannot be validated". If the validation fails, it produces UnprocessibleEntity<ValidationProblemDetails>.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 258 3/6/2025