Nullean.Entities.AspNetCore
0.1.1
dotnet add package Nullean.Entities.AspNetCore --version 0.1.1
NuGet\Install-Package Nullean.Entities.AspNetCore -Version 0.1.1
<PackageReference Include="Nullean.Entities.AspNetCore" Version="0.1.1" />
<PackageVersion Include="Nullean.Entities.AspNetCore" Version="0.1.1" />
<PackageReference Include="Nullean.Entities.AspNetCore" />
paket add Nullean.Entities.AspNetCore --version 0.1.1
#r "nuget: Nullean.Entities.AspNetCore, 0.1.1"
#:package Nullean.Entities.AspNetCore@0.1.1
#addin nuget:?package=Nullean.Entities.AspNetCore&version=0.1.1
#tool nuget:?package=Nullean.Entities.AspNetCore&version=0.1.1
Nullean.Entities.AspNetCore

Your domain rules answer bad requests with a standard 400 ValidationProblem, and you do not write the mapping.
Without this package, a rule that lives on your domain type is checked in each handler, and each handler turns the failures into a response its own way. This package validates the request body before the handler runs, turns a thrown ConstraintViolationException into the same response, and maps Result<T> and ApplyOutcome<T> to 200 or 400. Errors are keyed by Violation.Member, so a client can attach each message to a form field.
It builds on Nullean.Entities and Nullean.Entities.Extensions.DependencyInjection (installed with it). It is optional, it needs the Microsoft.AspNetCore.App shared framework, it uses no reflection, and it is trim and NativeAOT clean.
Install
Targets net10.0 and net11.0.
dotnet add package Nullean.Entities
dotnet add package Nullean.Entities.AspNetCore
Get going
A complete minimal API (Microsoft.NET.Sdk.Web). SignUp declares its rules next to the type; AddConstraints registers them.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Nullean.Entities;
using Nullean.Entities.AspNetCore;
using Nullean.Entities.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddConstraints<SignUp>().AddConstraintProblemDetails();
var app = builder.Build();
app.UseExceptionHandler();
app.MapPost("/signup", (SignUp s) => TypedResults.Ok(s.Email)).ValidateWith<RouteHandlerBuilder, SignUp>();
app.Run();
public sealed partial record SignUp(string Email, int Age)
{
[Constraint]
public static readonly Constraint<SignUp> HasEmail = Constraints<SignUp>.Check(
s => s.Email.Contains('@') ? [] : [Violation.Error(nameof(Email), "must be an email address")]
);
[Constraint]
public static readonly Constraint<SignUp> Adult = Constraints<SignUp>.Check(
s => s.Age >= 18 ? [] : [Violation.Error(nameof(Age), "must be 18 or older")]
);
}
curl -i -X POST localhost:5000/signup -H 'Content-Type: application/json' -d '{"email":"nope","age":3}'
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{"title":"One or more validation errors occurred.","status":400,"errors":{"Email":["must be an email address"],"Age":["must be 18 or older"]}}
(ASP.NET Core also adds a type field.) Every broken rule is reported at once, and the handler never runs. A valid body reaches the handler and returns 200.
The second example is the main value: a handler that changes an existing entity and lets a violation throw. Apply-style checks see the before and after, so rules like Transitions and Monotonic work, which a body-only filter cannot do.
// Registered with AddConstraintProblemDetails() + app.UseExceptionHandler()
app.MapPost("/signup/age", (SignUp current) =>
SignUp.Constraints.ApplyOrThrow(current, s => s with { Age = 1 })); // ConstraintViolationException becomes a 400
// Or return the outcome: Result<T> and ApplyOutcome<T> map to 200 / 400
app.MapPost("/signup/age2", (SignUp current) =>
SignUp.Constraints.Apply(current, s => s with { Age = 1 }).ToHttpResult());
What you get
Reject a bad request:
.ValidateWith<RouteHandlerBuilder, T>()on one endpoint, for aTthat declares its own constraints..ValidateWith(constraintSet)on one endpoint, for a companion's set..ValidateConstraints()on aMapGroup(or any endpoint builder): validates every handler argument whose type was registered withAddConstraints, and merges the errors of several arguments.
Turn exceptions and results into responses:
services.AddConstraintProblemDetails()registers problem details andConstraintViolationExceptionHandler; callapp.UseExceptionHandler()too. A handler can callApplyOrThroworUnwrapwith no try/catch.exception.ToProblem()returns the same 400 as anIResult, for code that catches the exception itself.result.ToHttpResult()onResult<T>: 200 with the value, or 400.outcome.ToHttpResult()onApplyOutcome<T>:AppliedandNoopanswer 200,Failureanswers 400. Warnings and hints of an applied change ride along in the 200 body as{ "value": ..., "notices": [...] }.
MVC controllers:
services.AddControllers().AddConstraintValidation()validates registered action arguments intoModelState; an[ApiController]answers with its usual 400.ModelState.AddViolations(violations, prefix)is public for code that validates by hand. Keys areprefix.Member.
Pitfalls and limits
- The filters validate the body only (
Validate, point-in-time rules). For a change to an existing entity, callApplyorApplyOrThrowin the handler so sequential rules see the before and after. ValidateWithtakes the first handler argument of typeT.ValidateConstraintslooks up each argument by its exact runtime type, so a derived type needs its own registration.- Only errors reject. Warnings and hints never produce a 400.
ConstraintFilter<T>and the registry are synchronous and pure: a rule that needs a database call belongs in the handler.- The key of an error is
Violation.Member, or the rule's id when the rule is not about one member. - Requires the
Microsoft.AspNetCore.Appframework reference, so it is not for class libraries without it.
Next
- Integration recipes: https://entities.nullean.net/integration/
- Getting started with constraints: https://entities.nullean.net/getting-started/
- Registering sets and validating options: https://www.nuget.org/packages/Nullean.Entities.Extensions.DependencyInjection
- Command-line apps: https://www.nuget.org/packages/Nullean.Entities.Argh
- Runnable example: https://github.com/nullean/entities/tree/main/examples/Nullean.Entities.Integration.Examples/AspNet
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. net11.0 is compatible. |
-
net10.0
- Nullean.Entities (>= 0.1.1)
- Nullean.Entities.Extensions.DependencyInjection (>= 0.1.1)
-
net11.0
- Nullean.Entities (>= 0.1.1)
- Nullean.Entities.Extensions.DependencyInjection (>= 0.1.1)
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 |
|---|---|---|
| 0.1.1 | 42 | 10/20/2026 |
| 0.1.0 | 128 | 10/20/2026 |
| 0.1.0-preview.2 | 52 | 10/20/2026 |
| 0.1.0-preview.1 | 44 | 10/20/2026 |