Slck.Envelope
1.2.0
dotnet add package Slck.Envelope --version 1.2.0
NuGet\Install-Package Slck.Envelope -Version 1.2.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="Slck.Envelope" Version="1.2.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Slck.Envelope" Version="1.2.0" />
<PackageReference Include="Slck.Envelope" />
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 Slck.Envelope --version 1.2.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: Slck.Envelope, 1.2.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 Slck.Envelope@1.2.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=Slck.Envelope&version=1.2.0
#tool nuget:?package=Slck.Envelope&version=1.2.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
Slck.Envelope
Slck.Envelope is a lightweight, zero-dependency library for ASP.NET Core (Minimal APIs and MVC) that wraps every response in a consistent ApiResponse<T> envelope — success, error, pagination, correlation ID, and timestamp included out of the box.
✨ Features
| Feature | Description |
|---|---|
| Consistent envelope shape | Every response carries success, data, error, meta, requestId, timestamp |
| Minimal API helpers | Envelope.Ok / Created / Accepted / NotFound / BadRequest / ... — full HTTP vocabulary |
| MVC action filter | AddEnvelopeFilter() wraps controller ObjectResult responses automatically |
| Global exception middleware | UseApiEnvelope() catches unhandled exceptions and writes a safe envelope |
| Exception → status mapping | Map NotFoundException → 404, ValidationException → 422, etc. in options — no try/catch in handlers |
[SkipEnvelope] opt-out |
Exclude file-download or health-check endpoints from envelope handling |
| Correlation ID propagation | Reads X-Correlation-ID from incoming request, echoes it in every response |
OnBeforeWriteError hook |
Attach OpenTelemetry traceId, tenant ID, or any custom field before error is written |
ToResult() extensions |
Bridge ApiResponse<T> from your domain/service layer to IResult without an HTTP dependency |
| Configurable JSON options | Plug in your own JsonSerializerOptions; defaults to camelCase, compact, nulls omitted |
| Stable JSON field order | [JsonPropertyOrder] guarantees success → data → error → meta → requestId → timestamp |
| Pagination support | PaginationMeta with Page, PageSize, Total, HasMore (1-based, mathematically correct) |
📦 Installation
dotnet add package Slck.Envelope
Targets net9.0 and depends only on Microsoft.AspNetCore.App (no extra NuGet packages).
🚀 Quick Start
1. Register (optional — all defaults are safe)
builder.Services.AddApiEnvelope(opts =>
{
opts.IncludeExceptionDetails = builder.Environment.IsDevelopment();
opts.CorrelationIdHeader = "X-Correlation-ID"; // default
// Map domain exceptions to HTTP status codes — no try/catch in handlers
opts.MapException<KeyNotFoundException>(404, "not_found")
.MapException<UnauthorizedAccessException>(401, "unauthorized");
// Attach OpenTelemetry trace ID to every error envelope
opts.OnBeforeWriteError = (ex, response) =>
response with { RequestId = Activity.Current?.TraceId.ToString() ?? response.RequestId };
});
2. Add middleware + (optional) MVC filter
app.UseApiEnvelope(); // global unhandled-exception handler
// For MVC controllers:
builder.Services.AddControllers().AddEnvelopeFilter();
3. Return envelopes from Minimal API endpoints
app.MapGet("/ticket/{id}", (string id) =>
{
var ticket = db.Find(id);
return ticket is null
? Envelope.NotFound($"Ticket '{id}' not found")
: Envelope.Ok(ticket);
});
app.MapGet("/tickets", (int page = 1, int pageSize = 10) =>
{
var meta = new PaginationMeta { Page = page, PageSize = pageSize, Total = total };
return Envelope.Ok(paged, meta);
});
app.MapPost("/ticket", (Ticket t) => Envelope.Created($"/ticket/{t.Id}", t));
app.MapPost("/jobs", (Job j) => Envelope.Accepted(new { j.Id, Status = "queued" }, $"/jobs/{j.Id}/status"));
📐 Response Shape
Success — 200 OK
{
"success": true,
"data": { "id": "123", "title": "Sample Ticket" },
"meta": { "page": 1, "pageSize": 10, "total": 3, "hasMore": false },
"requestId": "0HNK...",
"timestamp": "2026-04-16T08:00:00+00:00"
}
Error — 404 Not Found
{
"success": false,
"error": {
"code": "not_found",
"message": "Ticket '999' not found"
},
"requestId": "0HNK...",
"timestamp": "2026-04-16T08:00:01+00:00"
}
Validation Error — 422 Unprocessable Entity
{
"success": false,
"error": {
"code": "validation_error",
"message": "Validation failed",
"details": {
"id": ["Id is required."],
"title": ["Title is required."]
}
},
"requestId": "0HNK...",
"timestamp": "2026-04-16T08:00:02+00:00"
}
🛠️ Full API Reference
Envelope — Minimal API static helpers
| Method | Status |
|---|---|
Envelope.Ok(data, meta?) |
200 |
Envelope.Created(location, data) |
201 + Location header |
Envelope.Accepted(data, statusUrl?) |
202 + Location header |
Envelope.NoContent() |
204 |
Envelope.BadRequest(message, details?) |
400 |
Envelope.Unauthorized(message) |
401 |
Envelope.Forbidden(message) |
403 |
Envelope.NotFound(message) |
404 |
Envelope.Conflict(message) |
409 |
Envelope.UnprocessableEntity(errors, message) |
422 |
Envelope.TooManyRequests(message) |
429 |
Envelope.Error(message, code) |
500 |
Envelope.ServiceUnavailable(message) |
503 |
Envelope.From(response, statusCode) |
any (escape hatch) |
ApiResponseExtensions — bridge from domain layer to IResult
EnvelopeFactory.Ok(data).ToOkResult();
EnvelopeFactory.Fail<T>("not_found", "...").ToNotFoundResult();
EnvelopeFactory.Ok(job).ToAcceptedResult("/jobs/1/status");
response.ToResult(207); // any status code
[SkipEnvelope] — opt-out
// Minimal API
app.MapGet("/report/download", ...).WithMetadata(new SkipEnvelopeAttribute());
// MVC
[SkipEnvelope]
public class HealthController : ControllerBase { ... }
⚙️ EnvelopeOptions
| Property | Default | Description |
|---|---|---|
IncludeExceptionDetails |
false |
Include ex.Message in 500 responses (dev only) |
CorrelationIdHeader |
"X-Correlation-ID" |
Request/response correlation header |
JsonSerializerOptions |
null |
Override JSON serialization globally |
ExceptionMap |
{} |
Dictionary of Type → ExceptionMapping |
OnBeforeWriteError |
null |
Hook to enrich error envelopes before write |
📄 License
MIT — see LICENSE.
| 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 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. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.