Architecture
ArturRios.Util.WebApi fit together: the request pipeline, the security model, the response envelope, and the design…This page shows how the pieces of ArturRios.Util.WebApi fit together: the request pipeline, the
security model, the response envelope, and the design principles that hold them together.
Request pipeline
The library is a thin layer around the standard ASP.NET Core pipeline. WebApiStartup builds the
WebApplicationBuilder/WebApplication through one standard sequence, whose pipeline half adds the three
built-in middlewares with UseStandardMiddlewares() — AuthenticationMiddleware only when
AddTokenAuthentication was registered, with Swagger and CORS (when enabled) served just before it — then
any extra middlewares from WebApiStartupOptions.Middlewares,
then the controllers:
flowchart LR
Client["Client request"] --> Trace["TraceActivityMiddleware<br/><i>assigns/propagates W3C traceparent</i>"]
Trace --> Exception["ExceptionMiddleware<br/><i>catches unhandled exceptions</i>"]
Exception --> Auth["AuthenticationMiddleware<br/><i>validates token, attaches IAuthenticatedUser</i>"]
Auth --> Endpoint["Controller / endpoint"]
Endpoint --> Resolver["ToActionResult<br/><i>Output envelope → ActionResult</i>"]
Resolver --> ClientWebApiMiddleware is an abstract marker base class with no members — its only job is to let
UseWebApiMiddlewares (which WebApiStartup runs over Options.Middlewares) recognize which types are
safe to register with app.UseMiddleware(...). It registers each type in the order supplied, and
throws an ArgumentException for any type that isn’t a WebApiMiddleware:
public class Startup(string[] args) : WebApiStartup(args, options =>
{
options.Middlewares.Add(typeof(MyMiddleware)); // runs after the three built-in ones
})
{
// ...
}
// or, on a plain WebApplication:
app.UseStandardMiddlewares();
app.UseWebApiMiddlewares(typeof(MyMiddleware));
Because registration order is registration order, TraceActivityMiddleware runs first so the trace id
is available to everything downstream (including exception logging), ExceptionMiddleware runs next so
it can catch exceptions thrown by authentication or the endpoint itself, and AuthenticationMiddleware
runs last of the three so only requests that pass tracing/exception setup pay for token validation.
See Middleware & Diagnostics for what TraceActivityMiddleware and
ExceptionMiddleware do in detail, and Configuration for the full WebApiStartup
lifecycle.
Security model
AuthenticationMiddleware extracts a token (from the header, a cookie, or either, per
AuthenticationOptions.Source) and validates it on every request that isn’t a Swagger route or an
[AllowAnonymous] endpoint. Which token schemes are tried, and how, depends on AuthenticationOptions
(registered via AddTokenAuthentication):
flowchart TB
Token["Extracted token"] --> Jwt{"EnableJwt?"}
Jwt -- "yes" --> JwtValid{"Signature valid?"}
JwtValid -- "no" --> Google
JwtValid -- "yes" --> Mode{"JwtMode"}
Mode -- "ClaimsOnly (default)" --> Claims["mapper.FromClaims<br/><i>your claims, no lookup</i>"]
Mode -- "Revalidate" --> ProviderId["IAuthenticationProvider.GetAuthenticatedUserById<br/><i>Guid id, resolved per-request from RequestServices</i>"]
Jwt -- "no" --> Google{"EnableGoogle?"}
Google -- "yes" --> GoogleValid{"Google ID token valid?"}
GoogleValid -- "yes" --> ProviderEmail["IAuthenticationProvider.GetAuthenticatedUserByEmail"]
GoogleValid -- "no" --> R401["401 Unauthorized"]
Google -- "no" --> R401
ProviderId -.-> Cached["CachedAuthenticationProvider<br/><i>IMemoryCache, Ttl / CacheMisses</i>"]
ProviderEmail -.-> Cached
Cached -.-> ProviderId
Cached -.-> ProviderEmail
Claims --> Items["HttpContext.Items[User]"]
ProviderId --> Items
ProviderEmail --> Items
Items --> Authorize["AuthorizeAttribute<br/><i>401 if no user</i>"]
Authorize --> RoleReq["RoleRequirementFilter<br/><i>401 if no user, 403 if role not authorized</i>"]
RoleReq --> Endpoint["Controller action"]For the app JWT scheme, ClaimsOnly (the default) never touches a data store — the registered mapper
rebuilds the user straight from the token’s claims, so authentication costs nothing beyond the signature
check. Revalidate instead resolves IAuthenticationProvider from the current request’s
HttpContext.RequestServices and calls GetAuthenticatedUserById with the Guid from the token’s claims
on every request, trading a lookup for freshness. The Google scheme always resolves the user via
IAuthenticationProvider.GetAuthenticatedUserByEmail after verifying the token, so an
IAuthenticationProvider is required whenever Google authentication is
enabled, just as it is for JWT Revalidate. CachedAuthenticationProvider sits transparently in front of
any IAuthenticationProvider (registered via AddCachedAuthenticationProvider<T>) to absorb repeated
lookups of the same user within a short TTL, without any mode needing to know it’s there.
AuthorizeAttribute and RoleRequirementFilter both read the same HttpContext.Items["User"] slot that
AuthenticationMiddleware populates, and both honor [AllowAnonymous] — the library’s or ASP.NET Core’s
own, on the action or the controller — by short-circuiting before checking it.
See Security for the full authentication and authorization reference.
Response envelopes
Endpoints return an ArturRios.Output envelope rather than throwing on failure. ProcessOutput carries
success/error/message state; DataOutput<T> adds a typed payload on top of it:
classDiagram
class ProcessOutput {
+bool Success
+List~string~ Messages
+List~string~ Errors
+WithError(string) ProcessOutput
}
class DataOutput~T~ {
+T Data
+WithData(T) DataOutput~T~
}
class PaginatedOutput~T~ {
+int Page
+int PageSize
+int TotalCount
}
ProcessOutput <|-- DataOutput
DataOutput <|-- PaginatedOutputThe ToActionResult(...) extension maps any of ProcessOutput, DataOutput<T> or PaginatedOutput<T> onto
an ActionResult, defaulting the HTTP status to 200 when Success is true and 400 otherwise, unless an
explicit statusCode is supplied:
[HttpGet("{id:int}")]
public ActionResult<DataOutput<UserDto?>> GetById(int id)
{
DataOutput<UserDto?> output = _userService.GetById(id);
return output.ToActionResult();
}
See Responses for the full mapping reference.
Design principles
- Envelopes, not exceptions. Endpoints report failure through
ProcessOutput/DataOutput<T>and letToActionResultpick the status code;ExceptionMiddlewareis the safety net for anything that still escapes as a thrown exception, not the primary error-reporting path. - Stateless by default, revalidating when you need it.
JwtValidationMode.ClaimsOnlyis the default because most requests don’t need a fresh database read on every call;Revalidate(optionally cached) is there for the cases where staleness — instead of an expired token — is the risk you can’t accept. - Small, focused middlewares. Each of
TraceActivityMiddleware,ExceptionMiddlewareandAuthenticationMiddlewaredoes exactly one thing and derives from theWebApiMiddlewaremarker so it can be composed viaUseWebApiMiddlewaresin whatever order a given host needs. - Consistent
InvokeAsync. Every middleware follows the standard ASP.NET Core convention — a constructor capturingRequestDelegate nextplus other dependencies, and a singleInvokeAsync(HttpContext)method — so custom middlewares slot intoWebApiStartupOptions.Middlewares(orUseWebApiMiddlewares) the same way the built-in ones do. - Opinionated, but not locked in.
WebApiStartupruns one standard sequence and asks only forConfigureServices; every step of that sequence is also a public extension method, so a host that needs a different order composes the same pieces on a plainWebApplicationBuilder. - DI-first. Dependencies such as
IAuthenticationProvider,AuthenticationOptions, theITokenValidators andSettingsProviderare resolved through the container (constructor injection or, for per-request freshness,HttpContext.RequestServices) rather than passed around manually.
Where to next
- Configuration — the
WebApiStartuplifecycle andWebApiParameters. - Security — JWT validation modes, caching, and role-based authorization in depth.
- Middleware & Diagnostics — exception handling and distributed tracing.
- HTTP Client — building typed clients on top of
BaseWebApiClient. - Responses — the full
ToActionResultmapping reference. - Endpoint Toggling — enabling or disabling individual endpoints from code or configuration.