Decode.AspNetCore
3.0.0
dotnet add package Decode.AspNetCore --version 3.0.0
NuGet\Install-Package Decode.AspNetCore -Version 3.0.0
<PackageReference Include="Decode.AspNetCore" Version="3.0.0" />
<PackageVersion Include="Decode.AspNetCore" Version="3.0.0" />
<PackageReference Include="Decode.AspNetCore" />
paket add Decode.AspNetCore --version 3.0.0
#r "nuget: Decode.AspNetCore, 3.0.0"
#:package Decode.AspNetCore@3.0.0
#addin nuget:?package=Decode.AspNetCore&version=3.0.0
#tool nuget:?package=Decode.AspNetCore&version=3.0.0
Decode.AspNetCore
ASP.NET Core extensions for the Decode ecosystem, providing standardized infrastructure for APIs, including OpenAPI 3.0 Swagger configuration, User Context resolution, global exception handling, and base controllers integrated with Domain Notifications.
🚀 Features
- Swagger / OpenAPI 3.0 Integration: 1-line setup (
AddDecodeSwagger) with OpenAPI 3.0 HTTP Bearer authentication, environment security guard (UseDecodeSwagger), auto-discovery of XML documentation files, and native Swashbuckle escape hatches. IUserContext&UserContext: Web API implementation ofIUserContextusingIHttpContextAccessor, safely resolvingUserId, client IP address, User-Agent, Bearer access token, and custom claims.- GlobalExceptionMiddleware: A robust safety net that captures unhandled exceptions, logs them with a unique
errorId, and returns a standardized JSON response. - ApiControllerBase: A base controller that simplifies response handling by automatically checking for domain notifications and returning appropriate HTTP status codes.
- ApiResponse: A unified response model for all API endpoints:
{ success, data, errors, errorId }.
📦 Installation
Add the package to your API project:
dotnet add package Decode.AspNetCore
🛠️ Setup
1. Register Services in Program.cs
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// 1. Add Decode ASP.NET Core infrastructure (Domain Notifications + IUserContext)
builder.Services.AddDecodeAspNetCore();
// 2. Add Decode Swagger/OpenAPI 3.0
builder.Services.AddDecodeSwagger(options =>
{
options.Title = "My Enterprise API";
options.Version = "v1";
options.Description = "Authentication and Core API endpoints.";
options.EnableJwtBearer = true; // Enables native HTTP Bearer lock icon
options.IncludeXmlComments = true; // Auto-discovers all *.xml docs in execution folder
});
2. Configure HTTP Pipeline
WebApplication app = builder.Build();
// 1. Add global exception middleware at top of pipeline
app.UseGlobalExceptionMiddleware();
// 2. Enable Swagger UI (auto-disabled in Production unless forced)
app.UseDecodeSwagger();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
📖 Usage Examples
1. Using ApiControllerBase with Notifications
Inherit from ApiControllerBase to get automatic notification handling:
using Decode.AspNetCore.Controllers;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ApiControllerBase
{
private readonly IProductService _service;
public ProductsController(IProductService service)
{
_service = service;
}
[HttpPost]
public IActionResult Create(Product product)
{
_service.Create(product);
// If _service added notifications to IDomainNotificationContext,
// CreatedResponse() will automatically return 400 Bad Request (or custom status code).
// Otherwise, it returns 201 Created.
return CreatedResponse($"/api/products/{product.Id}", product);
}
}
2. Using IUserContext in Controllers or Services
using Decode.Context.Abstractions;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ProfileController(IUserContext userContext) : ControllerBase
{
[HttpGet]
public IActionResult GetMyProfile()
{
Guid currentUserId = userContext.UserId;
string clientIp = userContext.IpAddress;
return Ok(new
{
UserId = currentUserId,
Ip = clientIp,
IsAuthenticated = userContext.IsAuthenticated
});
}
}
Client IP address and proxies (changed in 2.0.0)
IpAddress returns Connection.RemoteIpAddress and does not read X-Forwarded-For by default.
Any client can set that header on its own request, so trusting it unconditionally — as versions before 2.0.0 did — let a caller choose the address recorded in audit logs, rate limiters and abuse heuristics.
The recommended setup is ASP.NET Core's own ForwardedHeadersMiddleware, which validates against a
configured list of known proxies and rewrites RemoteIpAddress before UserContext ever reads it:
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownProxies.Add(IPAddress.Parse("10.0.0.1"));
});
app.UseForwardedHeaders();
If you must read the header directly, opt in explicitly — only when the application sits behind a proxy you control that overwrites it:
builder.Services.AddDecodeAspNetCore(options => options.TrustForwardedHeaders = true);
IPv6 addresses are also preserved as-is. Earlier versions applied MapToIPv4() to every remote
address, which reinterprets the last four bytes of a genuine IPv6 address: 2001:db8::1 was
reported as 0.0.0.1. Only IPv4-mapped addresses (::ffff:203.0.113.10) are unwrapped now.
Standard Response Format
Success (200 OK / 201 Created):
{
"success": true,
"data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "Product A" },
"errors": null,
"errorId": null
}
Domain Notification Error (400 Bad Request):
{
"success": false,
"data": null,
"errors": ["Product name is required"],
"errorId": null
}
Unhandled Error (500 Internal Server Error):
{
"success": false,
"data": null,
"errors": ["An internal server error occurred. Please try again later."],
"errorId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}
errorId is Activity.Current?.Id when an activity is in flight — the W3C trace id above, which
correlates the response with the distributed trace and the logged [{ErrorId}] entry. It falls back
to HttpContext.TraceIdentifier (the shorter 0HMA1B2C3D4E5:00000001 form) when no activity exists.
Development shows more. Outside Development the
errorsarray carries only the generic message above. In Development it is replaced by the full exception chain —Type: messagefor the exception and every inner exception — plus a finalStackTrace: ...entry. That is deliberate for debugging, and it is why the environment check exists: do not run a public deployment withASPNETCORE_ENVIRONMENT=Development.
Two cases do not produce this envelope. If the client disconnects mid-request the middleware logs
at Information and returns nothing, since there is nobody left to receive it. If the response has
already started, the original exception is rethrown rather than overwritten — touching the status code
at that point would throw from inside the catch and replace the real fault with a misleading one.
📄 License
This project is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. 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. |
-
net10.0
- Decode.Context.Abstractions (>= 3.0.0)
- Decode.Notifications (>= 3.0.0)
- Swashbuckle.AspNetCore (>= 9.0.6)
-
net8.0
- Decode.Context.Abstractions (>= 3.0.0)
- Decode.Notifications (>= 3.0.0)
- Swashbuckle.AspNetCore (>= 9.0.6)
-
net9.0
- Decode.Context.Abstractions (>= 3.0.0)
- Decode.Notifications (>= 3.0.0)
- Swashbuckle.AspNetCore (>= 9.0.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.