Standardized API response envelope and global exception handling middleware for ASP.NET Core. Smart factory methods, automatic exception-to-status-code mapping, and native integration with AspNetCoreAuthKit and AspNetCoreHttpKit.
Why AspNetCoreResponseKit? Every project writes the same
ApiResponseclass and the sameExceptionHandlerMiddleware. AspNetCoreResponseKit gives you a production-ready implementation with zero boilerplate, consistent error formats, and smart factories that eliminate try/catch blocks in controllers.
- 📦
ApiResponse<T>— consistent response envelope for all endpoints - 🧠 Smart factories —
ApiResponse.From(value)andApiResponse.FromAsync(() => ...)handle null and errors automatically - 🌐 Middleware —
app.UseApiExceptionHandler()catches all unhandled exceptions globally - 🗺️ Automatic mapping — exceptions are mapped to HTTP status codes via a priority chain
- 🔗 AspNetCoreAuthKit integration —
HttpServiceException,RefreshTokenExceptionmapped automatically - 🔗 AspNetCoreHttpKit integration —
ApiResponse.From(HttpResult<T>)converts upstream results directly - ⚙️ Configurable — null status code, upstream propagation, exception details, custom mappings
| Requirement | Minimum version |
|---|---|
| .NET | 9.0+ |
| ASP.NET Core | 9.0+ |
dotnet add package AspNetCoreResponseKit// Program.cs
builder.Services.AddAspNetCoreResponseKit(opt =>
{
opt.IncludeExceptionDetails = builder.Environment.IsDevelopment();
opt.NullResponseStatusCode = HttpStatusCode.NotFound;
opt.PropagateUpstreamStatusCodes = true;
opt.DefaultErrorMessage = "An unexpected error occurred.";
// Custom exception mapping
opt.MapException<MyDomainException>(ex => (HttpStatusCode.UnprocessableEntity, ex.Message));
});
// Must be first in the middleware pipeline
app.UseApiExceptionHandler();
app.UseAuthentication();
app.UseAuthorization();[HttpGet("{id}")]
public async Task<ApiResponse<UserDto>> GetUser(int id, CancellationToken ct)
{
// Smart factory — returns 200 if found, configured null status code if not
return await ApiResponse.FromAsync(() => _service.GetUserAsync(id), ct: ct);
}
[HttpPost]
public async Task<ApiResponse<UserDto>> CreateUser(CreateUserRequest req, CancellationToken ct)
{
var user = await _service.CreateAsync(req, ct);
return ApiResponse.Created(user.ToDto(), "User created successfully.");
}
[HttpDelete("{id}")]
public async Task<ApiResponse> DeleteUser(int id, CancellationToken ct)
{
var deleted = await _service.DeleteAsync(id, ct);
return ApiResponse.FromBool(deleted, "User deleted.", "User not found.");
}app.MapGet("/users/{id}", async (int id, IUserService svc, CancellationToken ct) =>
{
var result = await ApiResponse.FromAsync(() => svc.GetUserAsync(id), ct: ct);
return result.ToResult();
});app.MapGet("/proxy/users/{id}", async (int id, IHttpService http, CancellationToken ct) =>
{
var result = await http.GetAsync<UserDto>($"/users/{id}", ct);
// Converts HttpResult<T> directly — status code propagated based on options
return ApiResponse.FromHttpResult(result).ToResult();
});{
"isSuccess": true,
"data": { "id": 1, "name": "Simone" },
"message": null,
"errors": [],
"statusCode": 200
}{
"isSuccess": false,
"data": null,
"message": "User not found.",
"errors": [],
"statusCode": 404
}When an unhandled exception is caught by the middleware, it is resolved in this order:
- Custom mappings — registered via
opt.MapException<T>() - AspNetCoreAuthKit / AspNetCoreHttpKit —
HttpServiceException,RefreshTokenException, etc. - Standard .NET exceptions —
ArgumentException,UnauthorizedAccessException, etc. - Fallback — 500 Internal Server Error
| Exception | Status code |
|---|---|
HttpNotFoundException |
404 |
HttpUnauthorizedException |
401 |
HttpForbiddenException |
403 |
HttpBadRequestException |
400 |
HttpConflictException |
409 |
HttpUnprocessableEntityException |
422 |
HttpTooManyRequestsException |
429 |
HttpServerErrorException |
500+ |
RefreshTokenException |
401 |
ArgumentNullException |
400 |
ArgumentException |
400 |
UnauthorizedAccessException |
401 |
KeyNotFoundException |
404 |
InvalidOperationException |
409 |
NotImplementedException |
501 |
TimeoutException |
408 |
OperationCanceledException |
499 |
| Method | Description |
|---|---|
ApiResponse.Ok(data) |
200 with data |
ApiResponse.Created(data) |
201 with data |
ApiResponse.NoContent() |
204 |
ApiResponse.NotFound<T>(message) |
404 |
ApiResponse.BadRequest<T>(message) |
400 |
ApiResponse.Unauthorized<T>(message) |
401 |
ApiResponse.Forbidden<T>(message) |
403 |
ApiResponse.Conflict<T>(message) |
409 |
ApiResponse.Error(message, statusCode) |
Custom error |
ApiResponse.From(value) |
Smart — null → configured status code |
ApiResponse.FromAsync(factory) |
Smart async — null → configured status code |
ApiResponse.FromBool(bool, onTrue, onFalse) |
Bool-based result |
ApiResponse.From(HttpResult<T>) |
From AspNetCoreHttpKit result |
| Method | Description |
|---|---|
.OnNull(message, statusCode?) |
Override response when data is null |
.OnSuccess(transform) |
Transform data on success |
.ToResult() |
Convert to IResult for Minimal APIs |
| Option | Type | Default | Description |
|---|---|---|---|
NullResponseStatusCode |
HttpStatusCode |
404 |
Status code when smart factory receives null |
NullResponseMessage |
string |
"Resource not found." |
Message when smart factory receives null |
PropagateUpstreamStatusCodes |
bool |
true |
Propagate status codes from upstream HTTP calls |
IncludeExceptionDetails |
bool |
false |
Include exception message and stack trace in errors |
DefaultErrorMessage |
string |
"An unexpected error occurred." |
Fallback error message |
AspNetCoreResponseKit is part of a cohesive set of lightweight, DI-ready toolkits for ASP.NET Core:
| Package | Description |
|---|---|
| AspNetCoreAuthKit | JWT, API Key, Refresh Tokens with rotation & dynamic claim policies |
| AspNetCoreResponseKit | Standardized ApiResponse<T>, global exception handling & smart factories |
| AspNetCoreHttpKit | Typed HTTP client with result pattern & StructLog integration |
| AspNetCoreCacheKit | Group-based caching with per-entry & per-group duration |
| StructLog | Structured logging with EventCode support & custom enrichers |
Each package works independently — use one or all five.
If you find AspNetCoreResponseKit useful, consider sponsoring its development.
MIT — see LICENSE for details.