-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
Is there an existing issue for this?
- I have searched the existing issues
Is your feature request related to a problem? Please describe the problem.
The way middleware is injected into the request execution pipeline is confusing and unintuitive. This is often confusing for both junior and senior developers as there are subtleties that are only found if you take the time and initiative to carefully read the related documentation, which in itself is confusing as the documentation relating to how all of the different components of the request execution pipeline interact together is spread out across different pages and sections of the ASP.NET documentation.
In the routing documentation, there's even a mention:
Apps typically don't need to call UseRouting or UseEndpoints. WebApplicationBuilder configures a middleware pipeline that wraps middleware added in Program.cs with UseRouting and UseEndpoints.
Slightly below it an another mention:
If the preceding example didn't include a call to UseRouting, the custom middleware would run after the route matching middleware.
In fact, considering that the term endpoint is well understood by most developers as simply being the destination route that gets served by the server, most .NET developer don't naturally associate UseEndpoints
with Minimal APIs due to the aforementioned fact that it is not required to be explicitly called for minimal API endpoints to function.
UseRouting
is also something that is most likely better understood by the developers and maintainers of the ASP.NET stack itself then by the average .NET developer. The documentation is sort of vague as to what function it actually does and how it's placement influences the rest of the request pipeline as explicitly calling it is optional and it doesn't seem to matter where it's placed as behavior does not appear to change regardless of where it's placed.
Some of the ordering of the middleware is so unintuitive that I still have to refer to the middleware order diagram to be able to remember that UseAuthentication
and UseAuthorization
have to go after UseRouting
.
The current new Web API project further reinforces the implicit implementation of the minimal API middleware as there's no mention of either UseRouting
nor UseEndpoints
. The template itself doesn't even conform to the current documentation nor expectations as the MapOpenApi
call is placed before UseHttpsRedirection
.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5)
.Select(index => new WeatherForecast(DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
)
)
.ToArray();
return forecast;
}
)
.WithName("GetWeatherForecast");
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
In my experience, most developers do not typically or instinctively refer to documentation during their day to day development, and this is further exacerbated in our new reality which is strongly AI-assisted. Request pipeline ordering is typically something that will lead most developers down a confusing debugging path that will likely end up with a hacky patch instead of simply adding the proper middleware hooks at the right place to ensure predictable and intuitive routing out of the box. I still don't comprehend why UseRouting
wouldn't go before UseStaticFiles
when static files are also part of the routing decisions.
With the current defaults, behavior will not reflect intention and a single line can introduce bugs that the developer may not understand.
As a simple example, if we imagine the following fictitious web app:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Minimal API Response");
app.Use((context, next) =>
{
if (context.Request.Path == "/")
{
context.Response.StatusCode = 200;
context.Response.ContentType = "text/plain";
context.Response.WriteAsync("Middleware Response");
return Task.CompletedTask;
}
return next();
}
);
app.Run();
The expectation by a developer unfamiliar with the ASP.NET request pipeline is that the minimal API endpoint would trigger first and "Minimal API Response" sent back. The actual behavior is that "Middleware Response" is returned.
If a hook to next()
is added after the response in the middleware as such:
app.Use((context, next) =>
{
if (context.Request.Path == "/")
{
context.Response.StatusCode = 200;
context.Response.ContentType = "text/plain";
context.Response.WriteAsync("Middleware Response");
next(); // Single line added here
return Task.CompletedTask;
}
return next();
}
);
Then the response becomes "Middleware ResponseMinimal API Response".
Only once the proper middleware hooks are in place does the behavior represent expectations:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseRouting();
app.MapGet("/", () => "Minimal API Response");
app.UseEndpoints(_ => { });
app.Use((context, next) =>
{
if (context.Request.Path == "/")
{
context.Response.StatusCode = 200;
context.Response.ContentType = "text/plain";
context.Response.WriteAsync("Middleware Response");
next();
return Task.CompletedTask;
}
return next();
}
);
app.Run();
Describe the solution you'd like
Proposed solutions and ideas:
- Improve documentation that better explains
UseRouting
, it's purpose and why its ordering matters. - Improve documentation that better explains
UseStaticFiles
and why it's not affected byUseRouting
. - Add a dedicated request pipeline documentation page that gathers all of the different aspects of middleware routing currently spread throughout various pages of documentation.
- Add an overload to
UseEndpoints()
that doesn't require passing an empty function (ie:app.UseEndpoints(_ => { });
). - Add additonal comments to the default .NET web templates commenting out various middlewares that are often used in the proper locations where they should be used with links to the related documentation.
Example proposal for the Web API template
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var app = builder.Build();
// Configure the HTTP request pipeline.
// To learn more about middleware orderings and the various middleware types, see the documentation above each method.
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/
app.UseHttpsRedirection();
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing
// app.UseRouting();
// https://learn.microsoft.com/en-us/aspnet/core/security/authentication/
// app.UseAuthentication();
// https://learn.microsoft.com/en-us/aspnet/core/security/authorization/introduction
// app.UseAuthorization();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5)
.Select(index => new WeatherForecast(DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
)
)
.ToArray();
return forecast;
}
)
.WithName("GetWeatherForecast");
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/middleware
// app.UseEndpoints(_ => { });
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Additional context
No response