Use this file to discover all available pages before exploring further.
The JordiAragonZaragoza.SharedKernel.Presentation.HttpRestfulApi package provides a set of ASP.NET Core building blocks that sit between your application layer and the HTTP transport. It ships abstract base controllers wired to the ICommandBus and IQueryBus, middleware for global exception handling, user identity propagation, and multi-tenant partition context extraction, plus rich result-to-HTTP-response mapping helpers based on Ardalis.Result. A companion Contracts package supplies shared request/response types and typed HttpClient extension methods that consuming services can reference without taking a dependency on the full implementation package.
Controllers, middlewares, response helpers, and DI registration. Referenced by your API host project.
HttpRestfulApi.Contracts
Shared DTOs (PaginatedRequest, PaginatedCollectionResponse<T>, FileResponse) and HttpClient helpers. Safe to reference from client or integration-test projects.
Call AddSharedKernelPresentationHttpRestfulApi from your Program.cs or service-registration extension. It returns the IServiceCollection for further chaining.
The registration method currently acts as an extension point. Your own registrations (e.g. AddSharedKernelApplication, authentication, etc.) must be added separately and before this call if they are depended on by the middleware pipeline.
3
Add middleware in the correct order
Register the three custom middlewares afterUseRouting() / UseAuthentication() but beforeMapControllers():
var app = builder.Build();app.UseMiddleware<ExceptionMiddleware>(); // 1. Catch-all for unhandled exceptionsapp.UseAuthentication();app.UseAuthorization();app.UseMiddleware<UserContextMiddleware>(); // 2. Populate IUserContextServiceapp.UseMiddleware<PartitionContextMiddleware>(); // 3. Populate IPartitionContextServiceapp.MapControllers();app.Run();
4
Derive your controllers
Inherit from BaseApiCommandController or BaseApiQueryController and use the built-in CommandBus / QueryBus properties to dispatch requests.
An abstract ControllerBase decorated with [ApiController] and [Authorize]. It lazily resolves ICommandBus from the current request’s IServiceProvider, so no constructor injection is needed in your concrete controller.
A catch-all middleware that wraps the entire pipeline. Any unhandled Exception that escapes downstream middleware is caught and serialised as a 500 Internal Server Error JSON response containing the HTTP status code and the exception message.
public class ExceptionMiddleware{ private readonly RequestDelegate next; public ExceptionMiddleware(RequestDelegate next) { this.next = next ?? throw new ArgumentNullException(nameof(next)); } public async Task InvokeAsync(HttpContext httpContext) { ArgumentNullException.ThrowIfNull(httpContext); try { await this.next(httpContext); } catch (Exception ex) { await HandleExceptionAsync(httpContext, ex); } } private static async Task HandleExceptionAsync(HttpContext context, Exception exception) { context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; var errorDetails = new { context.Response.StatusCode, exception.Message, }; await context.Response.WriteAsync(JsonSerializer.Serialize(errorDetails)); }}
Register in Program.cs:
app.UseMiddleware<ExceptionMiddleware>();
Register ExceptionMiddleware as the outermost middleware so it can catch exceptions from every subsequent middleware and controller in the pipeline.
Reads the KeyUserCorrelationId HTTP request header (referenced via UserConstants.UserId) and populates IUserContextService for use anywhere downstream (application layer, domain services, etc.).
public sealed class UserContextMiddleware{ private readonly RequestDelegate next; public UserContextMiddleware(RequestDelegate next) { this.next = next ?? throw new ArgumentNullException(nameof(next)); } public async Task InvokeAsync(HttpContext context, IUserContextService userContextService) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(userContextService); string? userId = context.Request.Headers[UserConstants.UserId]; if (userId is null) { if (AnonymousRequestHelper.IsAnonymousAllowed(context)) { userId = UserConstants.AnonymousUser; } else { context.Response.StatusCode = StatusCodes.Status400BadRequest; await context.Response.WriteAsync("Missing UserId headers."); return; } } userContextService.SetUserContext(userId!); await this.next(context); }}
Register in Program.cs:
app.UseMiddleware<UserContextMiddleware>();
Anonymous access to /swagger, /health, and /metrics paths is automatically allowed by AnonymousRequestHelper.IsAnonymousAllowed. For all other paths, the KeyUserCorrelationId header is required or the middleware returns 400 Bad Request.
A static helper used internally by UserContextMiddleware and PartitionContextMiddleware to decide whether a request path is allowed to bypass header validation.
public static class AnonymousRequestHelper{ public static bool IsAnonymousAllowed(HttpContext context) { ArgumentNullException.ThrowIfNull(context); var path = context.Request.Path.Value ?? string.Empty; // Infrastructure paths — no authentication or partition context required if (path.StartsWith("/swagger", StringComparison.InvariantCulture) || path.StartsWith("/health", StringComparison.InvariantCulture) || path.StartsWith("/metrics", StringComparison.InvariantCulture)) { return true; } return false; }}
To apply different anonymous-access rules in your application — for example, to allow unauthenticated access to an api/v1/auth/login endpoint — extend the conditions inside IsAnonymousAllowed or add your own middleware that runs before UserContextMiddleware and PartitionContextMiddleware.
Extension methods that convert an Ardalis.Result<T> or Ardalis.Result returned by the application layer into the appropriate ActionResult. The full status mapping is:
ResultStatus
HTTP Response
Body
Ok
200 OK / 204 No Content
Value or empty
Created
201 Created
Value + Location header
NoContent
204 No Content
—
Invalid
400 Bad Request
ValidationProblemDetails
NotFound
404 Not Found
ProblemDetails
Unauthorized
401 Unauthorized
ProblemDetails
Forbidden
403 Forbidden
—
Conflict
409 Conflict
ProblemDetails
Error
422 Unprocessable Entity
ProblemDetails
CriticalError
500 Internal Server Error
ProblemDetails
Unavailable
503 Service Unavailable
ProblemDetails
Usage — generic result:
// In a controller action:var result = await CommandBus.SendAsync(command, cancellationToken);return result.ToActionResult(this);// Or equivalently, call it on the controller:return this.ToActionResult(result);
Converts a collection of FluentValidation.ValidationFailure objects into a properly structured ProblemDetails or ValidationProblemDetails response. Used when you need to return validation errors that are not part of an Ardalis.Result — for example in custom filter attributes.
public static object BuildResponse( IReadOnlyCollection<ValidationFailure> failures, HttpContext context, int statusCode)
The helper selects the response shape based on statusCode. The switch explicitly matches 422, 404, and 409; all other codes fall through to the default branch which returns ValidationProblemDetails:
Status Code
Response Type
RFC Reference
422
ProblemDetails
RFC 9110 §422
404
ProblemDetails
RFC 7231 §6.5.4
409
ProblemDetails
RFC 9110 §409
any other
ValidationProblemDetails
RFC 7231 §6.5.1
// Example: manually build a 422 response from FluentValidation failuresvar failures = new List<ValidationFailure>{ new("OrderId", "Order not found.")};var response = ResponseBuilderHelper.BuildResponse(failures, HttpContext, 422);return UnprocessableEntity(response);
For teams using FastEndpoints instead of MVC controllers, EndpointExtensions.SendResponseAsync maps an Ardalis.IResult to the appropriate FastEndpoints response method.
public static async Task SendResponseAsync( this IEndpoint endpoint, IResult result, CancellationToken cancellationToken)
Status mapping mirrors ResultExtensions, adapting to FastEndpoints’ SendAsync, SendCreatedAtAsync, SendErrorsAsync, SendNoContentAsync, SendForbiddenAsync, and SendUnauthorizedAsync calls.
// Inside a FastEndpoints endpoint handler:public override async Task HandleAsync(CreateOrderRequest req, CancellationToken ct){ var command = new CreateOrderCommand(req.CustomerId, req.Items); var result = await CommandBus.SendAsync(command, ct); await this.SendResponseAsync(result, ct);}
The Contracts package ships a set of typed HttpClient helpers that are useful in integration tests, API gateways, or any service that calls another service over HTTP.
Builds a Uri from a route template, substituting path parameters first and appending any remainder as query-string entries.
public static Uri BuildUriWithQueryParameters( string basePath, params (string Key, string Value)[] queryParams)
// Substitutes {orderId} in the path template; PageNumber/PageSize go to the query stringUri uri = EndpointRouteHelpers.BuildUriWithQueryParameters( "/api/v1/orders/{orderId}/items", ("orderId", orderId.ToString()), ("PageNumber", "1"), ("PageSize", "20"));// → /api/v1/orders/abc-123/items?PageNumber=1&PageSize=20
var builder = WebApplication.CreateBuilder(args);// Application & infrastructure registrationsbuilder.Services.AddSharedKernelApplication(typeof(Program).Assembly);builder.Services.AddSharedKernelPresentationHttpRestfulApi();builder.Services.AddAuthentication(/* ... */);builder.Services.AddAuthorization();builder.Services.AddControllers();builder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();var app = builder.Build();// Middleware pipeline — order mattersapp.UseMiddleware<ExceptionMiddleware>(); // must be outermostif (app.Environment.IsDevelopment()){ app.UseSwagger(); app.UseSwaggerUI();}app.UseHttpsRedirection();app.UseAuthentication();app.UseAuthorization();app.UseMiddleware<UserContextMiddleware>(); // after authapp.UseMiddleware<PartitionContextMiddleware>(); // after user contextapp.MapControllers();app.Run();