Nedo.AspNet.ApiContracts
2.0.0
dotnet add package Nedo.AspNet.ApiContracts --version 2.0.0
NuGet\Install-Package Nedo.AspNet.ApiContracts -Version 2.0.0
<PackageReference Include="Nedo.AspNet.ApiContracts" Version="2.0.0" />
<PackageVersion Include="Nedo.AspNet.ApiContracts" Version="2.0.0" />
<PackageReference Include="Nedo.AspNet.ApiContracts" />
paket add Nedo.AspNet.ApiContracts --version 2.0.0
#r "nuget: Nedo.AspNet.ApiContracts, 2.0.0"
#:package Nedo.AspNet.ApiContracts@2.0.0
#addin nuget:?package=Nedo.AspNet.ApiContracts&version=2.0.0
#tool nuget:?package=Nedo.AspNet.ApiContracts&version=2.0.0
Nedo.AspNet.ApiContracts
Standardized API contracts for ASP.NET Core. One uniform request/response envelope, RFC 9457 error responses, W3C Trace Context / OpenTelemetry correlation, and Swagger filters that work out of the box. Built on top of Nedo.AspNet.Request.Validation.
Install
dotnet add package Nedo.AspNet.ApiContracts
30-second setup
using Nedo.AspNet.ApiContracts.Extensions;
using Nedo.AspNet.ApiContracts.Swagger;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApiContracts(); // controllers + validation + suppressions
builder.Services.AddGlobalExceptionHandler(); // RFC 9457 problem details on throw
builder.Services.AddSwaggerGen(o =>
{
o.OperationFilter<StandardHeaderFilter>();
o.AddStandardAuthorization();
});
var app = builder.Build();
app.UseExceptionHandler();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();
What you get
Every endpoint returns the same envelope:
// success
{
"success": true,
"title": "Products retrieved",
"status": 200,
"code": "SUC-RTV-001",
"data": [ /* … */ ],
"pagination": { "page": 1, "page_size": 20, "total_count": 100, "total_pages": 5, "has_next_page": true },
"info": {
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"trace_flags":"01",
"service_name":"nedo.product",
"correlation_id": "req-abc-123",
"language": "id-ID",
"timezone": "Asia/Jakarta"
}
}
// error (RFC 9457; Content-Type: application/problem+json)
{
"success": false,
"type": "urn:nedo:problems:ERR-NF-002",
"title": "Product not found",
"status": 404,
"detail": "No product with id=42",
"instance": "/api/products/42",
"code": "ERR-NF-002",
"info": { /* same trace fields */ }
}
Throw any exception inside an action and GlobalExceptionHandler produces the right envelope. Decorate request DTOs with validation attributes and the validation filter produces the same shape on bad input. No per-controller error-handling code.
Validation & safety defaults
The query/request contracts ship with validation attributes already applied. Malformed payloads are rejected at model binding before they reach your code.
Built-in on the query DSL (no extra setup):
[SafeIdentifier]on everyfield,table,column,navigation,alias,onslot — rejects anything outside^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$(max 128 chars). Defense-in-depth against SQL/LINQ injection through caller-supplied identifiers; doesn't replace per-entity allowlisting at the resolver, but ensures the shape is safe.[AllowedValues]onoperator,logic,direction,function,search_mode,join.type. Unknown values are rejected.- Bounded pagination:
page ≥ 1,page_size 1–1000, cursorlimit 1–1000,include.take 1–1000. - Bounded batches:
BatchRequest.data≤MaxBatchItems(1000),BatchQueryRequest.queries≤MaxBatchQueries(50). - Bounded filter nesting:
FilterGrouprecursion ≤FilterGroupLimits.MaxDepth(10). [Required]+[MaxLength(256)]onUpdateRequest.idandBatchItem.id.AggregateItem.fieldis conditionally required whenfunction ∈ {Sum, Avg, Min, Max}.
Exception → response mapping (GlobalExceptionHandler):
| Throws | → | Status / code |
|---|---|---|
ValidationException (System.ComponentModel.DataAnnotations) |
→ | 400 + structured field_errors / form_errors |
KeyNotFoundException |
→ | 404 ERR-NF-001 |
UnauthorizedAccessException |
→ | 401 ERR-AUTH-001 |
ArgumentException |
→ | 400 ERR-BIZ-001 |
InvalidOperationException |
→ | 409 ERR-BIZ-003 |
OperationCanceledException (when RequestAborted is cancelled) |
→ | 499 ERR-CLI-001, no body |
| anything else | → | 500 ERR-SRV-001 |
Header validation (opt-in middleware):
builder.Services.AddStandardHeaderValidation(opts =>
{
// Optional: tighten Tenant-Id to a UUID
opts.AddOrReplace(new HeaderRule {
Name = StandardHeaders.TenantId,
MaxLength = 36,
Pattern = new Regex(@"^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$")
});
});
// Place BEFORE UseExceptionHandler / MapControllers
app.UseStandardHeaderValidation();
Defaults rule out control characters (CR/LF — response-splitting / log-injection) and apply per-header length + format checks for X-Request-Id, X-Correlation-Id, X-Idempotency-Key, X-Tenant-Id, X-Client-Id, X-Role-Id, X-Client-Version, Accept-Language, X-Timezone. Authorization is intentionally not validated (owned by the auth middleware). Rejected requests get a 400 RFC 9457 problem with ERR-VAL-HDR-001.
Documentation
Full documentation lives in docs/. Eleven topic-focused guides:
| # | Doc | |
|---|---|---|
| 01 | Concept | Mental model, standards followed |
| 02 | Installation & Setup | NuGet install, what each call does |
| 03 | Standard Headers | Header reference + when to use each |
| 04 | Requests | BaseRequest<T>, the seven request types |
| 05 | Queries | Filter / search / sort / pagination DSL |
| 06 | Responses | BaseResponse<T> + per-operation info |
| 07 | Error Handling | RFC 9457 problem details + global handler |
| 08 | Observability | W3C Trace Context + OpenTelemetry |
| 09 | Swagger / OpenAPI | Filters, security schemes, multi-doc |
| 10 | Response Codes | Full code reference |
| 11 | Recipes | Copy-paste patterns (CRUD, batch, tests) |
New to the library? Read 01-Concept → 02-Installation → 11-Recipes in that order.
A runnable sample lives in samples/Nedo.AspNet.ApiContracts.Sample/ — every recipe wired to working code with .http files you can run from your IDE.
Standards
| Concern | Standard |
|---|---|
| Error responses | RFC 9457 — Problem Details for HTTP APIs |
| Trace correlation | W3C Trace Context |
| Service identity | OpenTelemetry — Resource Semantic Conventions |
| Date/time | RFC 3339 / ISO 8601 |
| Language tags | BCP 47 |
| HTTP semantics | RFC 9110 |
Versioning & Release
This project uses git tags for versioning. The GitLab CI/CD pipeline builds and publishes to NuGet.org on tag push.
git tag -a v2.0.0 -m "Release v2.0.0"
git push origin v2.0.0
The tag must start with v (e.g., v1.0.0, not 1.0.0). Prereleases follow SemVer: v1.1.0-beta.1.
Contributing
dotnet build
dotnet test # tests live under tests/Nedo.AspNet.ApiContracts.Tests
Open an MR against develop. Add tests for behavior changes — the suite uses xUnit and asserts envelope shape and OTel field correctness.
License
MIT
| Product | Versions 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. |
-
net9.0
- Nedo.AspNet.Request.Validation (>= 1.2.2)
- Swashbuckle.AspNetCore.Annotations (>= 7.2.0)
- Swashbuckle.AspNetCore.SwaggerGen (>= 7.2.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Nedo.AspNet.ApiContracts:
| Package | Downloads |
|---|---|
|
Nedo.AspNet.Authentication.Local
Local username/password authentication for Nedo.AspNet.Authentication with EF Core persistence. |
|
|
Nedo.AspNet.Exception.AspNetCore
ASP.NET Core integration for Nedo.AspNet.Exception: IExceptionHandler emitting Nedo.AspNet.ApiContracts BaseResponse<T> envelopes (RFC 9457), DI extensions, optional middleware. |
GitHub repositories
This package is not used by any popular GitHub repositories.