NPv.AspNetCore.Endpoints
0.2.0
dotnet add package NPv.AspNetCore.Endpoints --version 0.2.0
NuGet\Install-Package NPv.AspNetCore.Endpoints -Version 0.2.0
<PackageReference Include="NPv.AspNetCore.Endpoints" Version="0.2.0" />
<PackageVersion Include="NPv.AspNetCore.Endpoints" Version="0.2.0" />
<PackageReference Include="NPv.AspNetCore.Endpoints" />
paket add NPv.AspNetCore.Endpoints --version 0.2.0
#r "nuget: NPv.AspNetCore.Endpoints, 0.2.0"
#:package NPv.AspNetCore.Endpoints@0.2.0
#addin nuget:?package=NPv.AspNetCore.Endpoints&version=0.2.0
#tool nuget:?package=NPv.AspNetCore.Endpoints&version=0.2.0
NPv.AspNetCore.Endpoints
Reusable ASP.NET Core endpoint infrastructure for applications built with the NPv CQS and Result Pattern abstractions.
The package provides:
- an
IEndpointcontract for self-contained endpoint modules; - discovery and registration of endpoint modules from application assemblies;
- concise Minimal API mappings for commands with and without results;
- command mappings that construct a context using the current
IUserContext; - conversion of
ResultandResult<T>values to HTTP responses; - Data Annotations request validation through an endpoint filter.
Authentication and authorization policies remain the responsibility of the host application. The package does not depend on a particular authentication scheme, Auth domain, or application-specific model.
Status
This package is at an early stage (0.x). Its public API and runtime behavior may
change between minor versions.
Installation
dotnet add package NPv.AspNetCore.Endpoints
The consuming application uses ASP.NET Core and must provide the NPv CQS, execution-context, and result-pattern services required by its endpoints.
Defining an endpoint
Implement IEndpoint in an application or feature assembly:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using NPv.AspNetCore.Endpoints.Abstractions;
using NPv.AspNetCore.Endpoints.Mappings;
public sealed class CreateItemEndpoint : IEndpoint
{
public RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder endpoints) =>
endpoints
.MapCommand<CreateItemCommand>("/api/items")
.RequireAuthorization()
.WithTags("items");
}
Authorization is applied explicitly by the consuming endpoint and is not imposed by the package.
Discovering endpoints
Register endpoint modules from one or more assemblies in the application host:
using NPv.AspNetCore.Endpoints.Discovery;
app.MapEndpoints([
"MyApplication.Features",
"MyApplication.Pages"
]);
Discovered endpoint modules must implement IEndpoint and have a parameterless
constructor. Registration attaches ValidationFilter only to the
RouteHandlerBuilder returned by MapEndpoint. If a module maps several routes,
the other builders do not receive the filter automatically. Attach it explicitly
to each additional route that needs validation. Calling MapCommand or
MapCommandWithUser without discovery does not attach the filter either.
Validation boundary
ValidationFilter runs Data Annotations Validator.TryValidateObject on each
non-null bound endpoint argument before invoking the handler. It stops at the
first invalid argument, returning 400 Bad Request with that argument's errors;
the handler is not invoked. Null arguments are skipped.
The filter does not recursively traverse nested objects, collections or graphs.
It does not validate objects constructed later inside the handler, including the
context created by a MapCommandWithUser factory. Its coverage is limited to the
route builder to which it is attached and the arguments present at invocation.
Required command/application validation belongs to the CQS/Application boundary; see CQS execution and validation boundary.
Result responses
ToIResult converts successful and failed NPv results into ASP.NET Core
responses:
using NPv.AspNetCore.Endpoints.Helpers;
var result = await executor.ExecuteAsync(command, cancellationToken);
return result.ToIResult();
A successful Result produces 200 OK with an empty body. A successful
Result<T> produces 200 OK with its value serialized as JSON when non-null.
Result<T>.Success(null) also produces 200 OK, with an empty body (not JSON
null), through ASP.NET Core Results.Ok(null).
Success is determined by IsSuccess, not by the presence of a value. The
Result model permits nullable T and preserves
success for both Success(null) and implicit conversion of a null value.
Use a nullable type argument when null is an intended successful value.
Compatibility: null-valued typed success previously produced 400 with
{"errors":{}}. It now produces 200 with an empty body, consistent with the
Result model; clients relying on the old failure response must adjust.
Failed Result and Result<T> values produce 400 Bad Request with the envelope
below. These are reusable defaults; product-specific statuses and response bodies
belong to the consuming endpoint.
HTTP error envelope
With default ASP.NET Core JSON options, both ToIResult failures and
ValidationFilter failures produce 400 with this shape:
{ "errors": { "property": ["code"] } }
errors is lowercase. Each key maps to an array of codes, including repeated
codes; this is not an ASP.NET Core Problem Details body. Binding failures and
other host responses are outside this envelope's scope. Host JSON configuration
can change serialization, including dictionary-key casing.
| Source | Field key | Array values |
|---|---|---|
ToIResult |
Grouped by Error.PropertyName, preserving explicit names and casing (Title stays Title). Null becomes request; empty stays "". |
Error.Code unchanged. A failure without errors yields {"errors":{}}. |
ValidationFilter |
Grouped by the first ValidationResult.MemberNames entry; absent/null becomes request, empty stays "". Only the first character is lowercased (Title → title, URL → uRL); additional member names are ignored. |
Mapped from the exact ValidationResult.ErrorMessage string using the table below. |
The casing difference is retained for compatibility; clients must not assume
identical field keys across these two failure paths. The filter groups before
lowercasing, so distinct names that collapse to one key (such as Title and
title) cause dictionary construction to throw instead of merging errors.
| Exact validation error message | HTTP code |
|---|---|
RequiredAttribute |
validation.required |
EmailAddressAttribute |
validation.invalid_format |
StringLengthAttribute or MinLengthAttribute |
validation.too_short |
| Any other message, including null, default/localized messages or an existing code | validation.invalid_value |
The filter matches message text, not attribute type. For example,
[Required(ErrorMessage = nameof(RequiredAttribute))] maps to
validation.required; ordinary [Required] uses a default message and maps to
validation.invalid_value.
Author's Note
This library grew out of my long-standing personal interest in structuring and publishing open source packages. Over time, I’ve revisited and refined earlier internal utilities and ideas, giving them a more consistent shape and preparing them for wider reuse. Along the way, I’ve also taken the opportunity to explore how open source distribution and licensing work in the .NET ecosystem.
It’s a small step toward something I’ve always wanted to try — sharing practical, minimal tools that reflect years of learning, experimentation, and refinement.
Hopefully, someone finds it useful.
Nikolai 😛
⚖️ License
MIT — you are free to use this in commercial and open-source software.
| 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. |
-
net10.0
- NPv.CQS.Abstractions (>= 3.0.0)
- NPv.ExecutionContext.Abstractions (>= 2.0.0)
- NPv.ResultPattern (>= 2.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on NPv.AspNetCore.Endpoints:
| Package | Downloads |
|---|---|
|
NPv.Auth.AspNetCore
ASP.NET Core endpoints, cookie handling, claims helpers, and service registration for NPv authentication modules. |
GitHub repositories
This package is not used by any popular GitHub repositories.