Description
Related #43352
There are scenarios where it is desirable to affect the behavior of endpoint-aware middleware that runs in a request pipeline that will be handled by a non-endpoint-aware middleware (rather than an actual endpoint) such that the endpoint-aware middleware performs its operations as if the request-handling middleware were actually an endpoint.
For example, the static files middleware handles requests but does not do so via endpoints. Rather, it is a terminal middleware for requests with paths that map to static files in the configured file provider. The authorization middleware is an endpoint-aware middleware that uses metadata from the current request endpoint to perform authorization actions and will no-op for requests with no active endpoint. This means that one can't use the authorization middleware to enforce authorization for static files. The output cache middleware is similar.
The repo AspNetCorePathAuthorization demonstrates a concept of "metadata-only endpoints" that allows endpoints to be registered that have only metadata, and no actual endpoint handler. These endpoints only exist for the purpose of effectively adding metadata to arbitrary route paths, either adding metadata to existing real endpoints, or providing metadata for requests with no real endpoint but with a matching path. This metadata can then be used by endpoint-aware middleware to perform operations relevant for the current request.
An example of what this API could look like:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Add metadata to every request in the app
app.MapMetadata("/{**subpath}", new { Whatever = "This is on every request now!" });
// Add metadata to all requests under /public in the app
app.MapMetadata("/public/{**subpath}", new MessageMetadata("Hello from /public!"));
// Potentially terminal middleware
app.Use(async (ctx, next) =>
{
// This obviously isn't a good example but something like the static files middleware effectively does this
if (ctx.Request.Path.StartsWithSegments("/public"))
{
var message = ctx.GetEndpoint()?.Metadata.GetMetadata<MessageMetadata>()?.Message;
await ctx.Response.WriteAsync($"{message ?? "No message metadata found :("}");
return;
}
await next();
});
app.Run();
record MessageMetadata(string Message);
Higher level APIs like Authorization and Output Caching could be updated to leverage this with new top-level APIs for configuring authorization and output caching based on path, e.g.:
// Authorize all requests under /users
app.RequireAuthorization("/users");
// Disable authorization for all requests under /public
app.AllowAnonymous("/public");
// Cache all requests under /expensivestuff
app.CacheOutput("/expensivestuff");