Repository navigation
Middleware
Middleware components inspect, modify, or short-circuit requests as they flow through the pipeline. ArgusTransfer supports two levels of middleware: global and per-endpoint.
All middleware implements the IArgusMiddleware interface:
public interface IArgusMiddleware
{
Task InvokeAsync(ArgusContext context, ArgusRequestDelegate next);
}- Call
await next(context)to pass control to the next middleware (or the handler) - Skip calling
nextto short-circuit the pipeline -- setcontext.Responsedirectly and return
Global middleware runs for every request, regardless of which endpoint is matched. Register it in your module's AddRoutes method:
public void AddRoutes(IArgusRouteBuilder app)
{
app.UseMiddleware(new ArgusLoggingMiddleware(logger));
app.MapGet("/items", handler);
app.MapPost("/items", handler);
}Global middleware executes in registration order. The first registered middleware is the outermost layer.
When you use AddArgusModules(), two built-in middleware registered through dependency injection are placed before any middleware your modules add, regardless of registration order:
-
ArgusExceptionHandlerMiddleware-- whenservices.AddArgusExceptionHandler()is called (outermost) -
ArgusAuthenticationMiddleware-- whenservices.AddArgusAuthentication<THandler>()is called - global middleware added by modules with
app.UseMiddleware(...), in registration order - per-endpoint middleware added with
.WithMiddleware(...)
Per-endpoint middleware runs only for a specific route. Attach it using the WithMiddleware method on the endpoint convention builder:
app.MapPost("/items", handler)
.WithMiddleware(new MyValidationMiddleware());Per-endpoint middleware runs after global middleware in the pipeline.
Request arrives
|
v
Global Middleware 1 (outermost)
|
v
Global Middleware 2
|
v
Per-Endpoint Middleware 1
|
v
Per-Endpoint Middleware 2
|
v
Endpoint Handler
|
v (unwinds back through middleware)
Per-Endpoint Middleware 2
|
v
Per-Endpoint Middleware 1
|
v
Global Middleware 2
|
v
Global Middleware 1
|
v
Response sent
ArgusTransfer includes ArgusLoggingMiddleware, which logs the start and completion of each request using ILogger<ArgusLoggingMiddleware>:
On request entry:
ARGUS {Verb} {Route} [{CorrelationToken}]
On response completion:
ARGUS {Verb} {Route} [{CorrelationToken}] -> {StatusCode} in {ElapsedMs}ms
When the handler (or a later middleware) throws or is cancelled, as a warning:
ARGUS {Verb} {Route} [{CorrelationToken}] -> failed in {ElapsedMs}ms
The exception is not part of this entry; it propagates unchanged and is logged by ArgusExceptionHandlerMiddleware or the pipe host. If the logging middleware runs outside an exception handler that turns the exception into a response, it sees a normal completion with that response's status code instead.
Usage:
public void AddRoutes(IArgusRouteBuilder app)
{
var logger = loggerFactory.CreateLogger<ArgusLoggingMiddleware>();
app.UseMiddleware(new ArgusLoggingMiddleware(logger));
// ... register routes
}Catches unhandled exceptions thrown by later middleware or the endpoint handler, logs them with verb, route and correlation token, and replaces the response with a 500 Internal Server Error problem details response whose instance is the correlation token. By default the response contains only a generic message (ArgusExceptionHandlerMiddleware.GenericErrorDetail), so no internal details leak to clients.
services.AddArgusModules();
services.AddArgusExceptionHandler(options =>
{
// Adds the exception message, type and stack trace to the response. Development only!
options.IncludeExceptionDetails = environment.IsDevelopment();
});With IncludeExceptionDetails, detail is the exception message and the extension members exceptionType and exception (the full ToString()) are added.
Cancellation caused by context.RequestAborted (the request timeout or server shutdown) is not handled, so the host still answers a timed-out request with 503 Service Unavailable. Other OperationCanceledExceptions -- e.g. a downstream call timing out -- become 500.
Even without this middleware, the pipe host never drops the connection on an unhandled exception: it logs it and returns a generic 500 problem details response (see Error Handling). Register the middleware to control the response, for example to include details during development.
Authenticates every request with a pluggable IArgusAuthenticationHandler, sets context.User, and rejects requests to protected endpoints with 401 or 403 problem details. See Authentication.
This middleware rejects requests without a custom header and short-circuits with 400 Bad Request (for authentication, use the built-in Authentication support instead):
public class RequireClientIdMiddleware : IArgusMiddleware
{
public async Task InvokeAsync(ArgusContext context, ArgusRequestDelegate next)
{
if (!context.Request.Headers.ContainsKey("X-Client-Id"))
{
context.Response = ArgusProblemDetails.BadRequest("The X-Client-Id header is required.");
return; // short-circuit -- do not call next
}
await next(context);
}
}This middleware measures how long the downstream pipeline takes and adds the elapsed time as a response header:
public class TimingMiddleware : IArgusMiddleware
{
public async Task InvokeAsync(ArgusContext context, ArgusRequestDelegate next)
{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
if (context.Response != null)
{
context.Response.Headers["X-Elapsed-Ms"] =
stopwatch.ElapsedMilliseconds.ToString();
}
}
}Use context.Items to pass data through the pipeline:
// In middleware
context.Items["UserId"] = "user-123";
await next(context);
// In handler
var userId = (string)context.Items["UserId"];The authenticated caller has its own typed property, context.User (a ClaimsPrincipal), set by ArgusAuthenticationMiddleware -- see Authentication.
copyright @ Sam Gerené