DotThrow.AspNetCore
2.0.2
dotnet add package DotThrow.AspNetCore --version 2.0.2
NuGet\Install-Package DotThrow.AspNetCore -Version 2.0.2
<PackageReference Include="DotThrow.AspNetCore" Version="2.0.2" />
<PackageVersion Include="DotThrow.AspNetCore" Version="2.0.2" />
<PackageReference Include="DotThrow.AspNetCore" />
paket add DotThrow.AspNetCore --version 2.0.2
#r "nuget: DotThrow.AspNetCore, 2.0.2"
#:package DotThrow.AspNetCore@2.0.2
#addin nuget:?package=DotThrow.AspNetCore&version=2.0.2
#tool nuget:?package=DotThrow.AspNetCore&version=2.0.2
DotThrow.AspNetCore
ASP.NET Core support for DotThrow validators:
- Minimal APIs:
.WithValidation<T>()on an endpoint or a route group. Works with Native AOT and the request delegate generator. - MVC: a global action filter (
AddDotThrowValidation()) and a[ValidateWithDotThrow]attribute. ValidationReportto RFC 7807 validation problem helpers.
Targets .NET 8, .NET 9 and .NET 10. It depends on DotThrow and the ASP.NET Core shared framework.
Register validators with DotThrow.DependencyInjection.
Install
dotnet add package DotThrow.AspNetCore
dotnet add package DotThrow.DependencyInjection
Packages
The DotThrow packages share one version:
- DotThrow: guard clauses, collect mode,
Validator<T>, dynamic rules, message translations and analyzers. No dependencies. - DotThrow.DependencyInjection: register validators in dependency injection, validate options classes, and dynamic validators from configuration.
- DotThrow.AspNetCore (this package): validation for minimal API endpoints and MVC actions, and validation problem helpers.
Minimal APIs
using DotThrow;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddValidator<SignUp, SignUpValidator>();
var app = builder.Build();
app.MapPost("/sign-up", (SignUp request) => TypedResults.Ok())
.WithValidation<SignUp>();
app.Run();
public sealed record SignUp(string? Name, string? Email);
public sealed class SignUpValidator : Validator<SignUp>
{
public SignUpValidator()
{
For(x => x.Name, name => name.IfNullOrWhiteSpace());
For(x => x.Email, email => email.IfNullOrWhiteSpace().IfNotEmailAddress());
}
}
An invalid request never reaches the handler. The answer is a 400 application/problem+json:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Email": ["'Email' must be a valid email address."]
}
}
The filter finds the handler parameters of type T once, when the endpoints are built. A parameter
whose type derives from T is validated too. Parameters from services ([FromServices],
[FromKeyedServices], or a registered service type without a binding attribute) are never
validated, and an optional argument that the request did not send is skipped. When several
arguments are validated, the answer has all their failures, named after the parameter
("first.Value", "second.Value").
Setup mistakes (a handler without a T parameter from the request, no IValidator<T>
registered, JSON options that cannot write a validation problem) never break other endpoints:
ASP.NET Core builds the endpoints at the first request, the mistake is logged as an error, and only
that endpoint answers 500. To stop the app at startup instead, with every mistake listed:
builder.Services.ValidateEndpointsOnStart();
More forms:
// Groups.
app.MapPost("/orders", (Order order) => TypedResults.Ok()).WithValidation<Order>("checkout");
// A validator object, no registration needed.
app.MapPost("/notes", (Note note) => TypedResults.Ok()).WithValidation(new NoteValidator());
// Every endpoint of a group that has an Order parameter. Other endpoints are not changed.
var orders = app.MapGroup("/orders").WithValidation<Order>();
// Per-request options and a custom response.
app.MapPost("/admin/users", (CreateUser user) => TypedResults.Ok()).WithValidation<CreateUser>(o =>
{
o.Groups = ["admin"];
o.ConfigureValidation = (options, http) => options.Items["user"] = http.User.Identity?.Name;
o.ResultFactory = (report, http) => report.ToValidationProblem(new ValidationProblemOptions { IncludeErrorCodes = true });
});
Validated endpoints also describe the 400 response for OpenAPI.
MVC
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
builder.Services.AddControllers().AddDotThrowValidation();
Every action argument whose type has an IValidator<T> registered is validated before the action
runs, and each failure becomes a ModelState error named by its property path.
[ApiController]actions do not run: the answer is the app's usual invalid model state response, a 400ValidationProblemDetailsby default.- Other actions run and check
ModelState.IsValidthemselves, as with data annotations.
Without the global filter, put [ValidateWithDotThrow] on a controller or an action. The closest
setting wins, so an attribute can change the groups or turn validation off for one action:
[ApiController]
[Route("orders")]
public sealed class OrdersController : ControllerBase
{
[HttpPost]
[ValidateWithDotThrow("checkout")]
public IActionResult Create(Order order) => Ok();
[HttpPost("draft")]
[ValidateWithDotThrow(Skip = true)]
public IActionResult SaveDraft(Order order) => Ok();
}
AddDotThrowValidation(o => ...) sets the default groups and a per-request ConfigureValidation.
Error codes and severities
builder.Services.Configure<ValidationProblemOptions>(o =>
{
o.IncludeErrorCodes = true;
o.IncludeSeverities = true;
});
Both filters then add two extensions, keyed and ordered like errors (keys follow the JSON
options' DictionaryKeyPolicy, the same as errors):
"errorCodes": { "Email": ["NotEmailAddress"] },
"severities": { "Email": ["Error"] }
The value that failed (ValidationFailure.AttemptedValue) is never written to a response, because
it may be a password or a token.
Helpers
var report = await validator.ValidateAsync(request);
if (!report.IsValid)
{
return report.ToValidationProblem(); // minimal APIs: TypedResults.ValidationProblem
}
HttpValidationProblemDetails details = report.ToProblemDetails(); // minimal APIs, IProblemDetailsService
ValidationProblemDetails mvcDetails = report.ToValidationProblemDetails(); // MVC: BadRequest(mvcDetails)
Native AOT
.WithValidation<T>()uses no reflection per request and works with the request delegate generator (PublishAot). Register validators withAddValidator<TModel, TValidator>(), not assembly scanning.- Call
builder.Services.AddProblemDetails(). It gives the app's JSON options the metadata that everyValidationProblemresult needs in Native AOT. Without it the endpoint is a setup mistake (see above) unless you set aResultFactory, instead of answering 500 only to invalid requests. - The
errorCodesandseveritiesextensions are written as JSON elements, so they need no entries in yourJsonSerializerContext. When you callToValidationProblem()orToProblemDetails()yourself with aDictionaryKeyPolicy, pass it askeyPolicyso the keys matcherrors.ToValidationProblemDetails()(MVC) uses dictionaries instead, which every MVC formatter can write. - MVC itself does not support Native AOT, so the MVC filter does not either.
Links
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. 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 is compatible. 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. |
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 |
|---|---|---|
| 2.0.2 | 87 | 10/11/2026 |
| 2.0.1 | 91 | 10/10/2026 |
| 2.0.0 | 89 | 10/10/2026 |
| 2.0.0-rc.1 | 52 | 10/10/2026 |