-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Support SkipStatusCodePages on endpoints and authorized routes #38509
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support SkipStatusCodePages on endpoints and authorized routes #38509
Conversation
f767624
to
40b66e7
Compare
@@ -41,10 +42,12 @@ public async Task Invoke(HttpContext context) | |||
{ | |||
var statusCodeFeature = new StatusCodePagesFeature(); | |||
context.Features.Set<IStatusCodePagesFeature>(statusCodeFeature); | |||
var endpoint = context.GetEndpoint(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having context.GetEndpoint();
before _next(context)
implies that UseStatusCodePages
should be after UseRouting
which might not be the case everywhere, including the sample in the docs.
This could be after _next(context)
and also only be called if statusCodeFeature.Enabled
is true
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a fair consideration, and we would want to make sure the doc was accurate.
Do we have examples of anything else that's endpoint aware but placed before UseRouting?
This ordering was recommended so that the attribute behavior could be applied early and then overridden by other middleware/code while processing the request. That's consistent with how the filter works today. If placed after _next, you wouldn't be able to tell if IStatusCodePagesFeature had the default value or if it was set by something in the app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Tratcher Sorry, I'm confused a little bit! Are you saying that going forward StatusCodePagesMiddleware
should always be after RoutingMiddleware
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StatusCodePagesMiddleware can stay where it is and continue to work for existing apps, but if you want it to be route aware and use the SkipStatusCodePages attribute then it will need to be placed after routing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see! The implementation has been changed after I add the original comment. It's all clear now.
I assume if StatusCodePagesMiddleware with re-execute would be used after routing it will automatically add the routing, right?
aspnetcore/src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesExtensions.cs
Lines 179 to 189 in 4656f15
if (app.Properties.TryGetValue(globalRouteBuilderKey, out var routeBuilder) && routeBuilder is not null) | |
{ | |
return app.Use(next => | |
{ | |
RequestDelegate? newNext = null; | |
// start a new middleware pipeline | |
var builder = app.New(); | |
// use the old routing pipeline if it exists so we preserve all the routes and matching logic | |
// ((IApplicationBuilder)WebApplication).New() does not copy globalRouteBuilderKey automatically like it does for all other properties. | |
builder.Properties[globalRouteBuilderKey] = routeBuilder; | |
builder.UseRouting(); |
What if StatusCodePagesFeature
set the Enable property lazily? In this case it doesn't matter where StatusCodePagesMiddleware
is as long as the feature is accessed after routing, right?
public class StatusCodePagesFeature : IStatusCodePagesFeature
{
private readonly HttpContext httpContext;
public StatusCodePagesFeature(HttpContext httpContext)
{
_httpContext = httpContext;
}
private bool? _enabled;
public bool Enabled
{
get
{
if (_enabled == null)
{
var endpoint = _httpContext.GetEndpoint();
var skipStatusCodePageMetadata = endpoint?.Metadata.GetMetadata<ISkipStatusCodePagesMetadata>();
if (skipStatusCodePageMetadata is not null)
{
_enabled = false;
}
else
{
_enabled = true;
}
}
return _enabled;
}
set
{
_enabled = value;
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StatusCodePagesFeature is a public class, so if we were to change it, we'd have to make it continue to work as it does today when called with the empty constructor. I think it might be worthwhile to just use a new internal IStatusCodePagesFeature that lazily reads the endpoint.
The only thing I don't like is locking in the wrong value for Enabled if the property is read before UseRouting(). You could definitely argue it's no worse than today where it's locked in immediately when the feature is added, but that feels a little less surprising. I wonder if it'd be better to avoid negative caching unless the property is explicitly set, but maybe that's even more surprising.
Thank you for your API proposal. I'm removing the |
src/Http/Http.Abstractions/src/Metadata/IStatusCodePagesMetadata.cs
Outdated
Show resolved
Hide resolved
src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesMiddleware.cs
Outdated
Show resolved
Hide resolved
src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesMiddleware.cs
Outdated
Show resolved
Hide resolved
|
||
/// <summary> | ||
/// Defines a contract used to specify metadata for skipping the StatusCodePage | ||
/// middleware in <see cref="Endpoint.Metadata"/>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we drop the in Endpoint.Metadata
? I don't know if it helps clarify things?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the in Endpoint.Metadata
. What's wrong with it? It could point the curious to API docs for endpoint routing.
@@ -283,4 +284,38 @@ public async Task Reexecute_WorksAfterUseRoutingWithGlobalRouteBuilder() | |||
var content = await response.Content.ReadAsStringAsync(); | |||
Assert.Equal("errorPage", content); | |||
} | |||
|
|||
[Fact] | |||
public async Task SkipStatusCodePages_SupportsEndpoints() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a test that verifies we let people change the feature in the body of the endpoint?
endpoints.MapGet("/", [SkipStatusCodePages] (c) =>
{
c.GetFeature<IStatusCodePagesFeature>().Enabled = false;
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except verify that you can set it to true
! Although it really would be weird to reenable it after adding the attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems fair! I missed seeing this before the auto-merge kicked it but I'll address in a follow-up.
@captainsafia, @Kahbazi's comment makes me realize we designed this wrong. |
Hm. Will dig into this to understand the flow here and file a new issue. |
Background and Motivation
#10317 identifies an issue where the behavior of the
AuthorizationMiddleware
circumvents the filter logic inSkipStatusCodeAttribute
and results in theStatusCodePageMiddleware
being executed when it shouldn't and masking the underlying 401 from theAuthorizationMiddleware
.This PR attempts to resolve this issue by removing the dependency on the
IFilter
execution order from theSkipStatusCodeAttribute
by introducing anISkipStatusCodesMetadata
interface.Proposed API
Usage Examples
Closes #10317