Skip to content

Update ExceptionHandler middleware pipeline construction to reinvoke UseRouting in error branch #34991

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

Closed
wants to merge 6 commits into from

Conversation

JunTaoLuo
Copy link
Contributor

@JunTaoLuo JunTaoLuo commented Aug 3, 2021

Fixes #34146

@ghost ghost added the area-runtime label Aug 3, 2021
@JunTaoLuo JunTaoLuo changed the title Johluo/diagnostic option3 Update ExceptionHandler middleware pipenline construction to reinvoke UseRouting in error branch Aug 3, 2021
app.UseRouting();
app.UseRouting(overrideEndpointRouteBuilder: false);

// Copy the endpoint route builder to the built application
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit hacky but required to ensure the EndpointRouteBuilder is shared between _builtApplication and app. We should probably invest in a cleaner approach for sharing the Properties bag between the two builders.

@JunTaoLuo JunTaoLuo force-pushed the johluo/diagnostic-option3 branch from 7482973 to 575aa50 Compare August 3, 2021 17:13
@@ -95,7 +99,22 @@ public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder a
throw new ArgumentNullException(nameof(options));
}

return app.UseMiddleware<ExceptionHandlerMiddleware>(Options.Create(options));
return app.Use(next =>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All middlewares that modifies paths will now need to follow this pattern to work for Minimal APIs. I don't think this is very intuitive though.

Also, a similar fix is likely needed for RewriteMiddleware

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs to be more specific, we probably need to distinguish between minimal API vs regular apps to figure out if we need to handle the middleware differently.

/// <see cref="Endpoint"/> associated with the <see cref="HttpContext"/>.
/// </para>
/// </remarks>
public static IApplicationBuilder UseRouting(this IApplicationBuilder builder, bool overrideEndpointRouteBuilder)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overrideEndpointRouteBuilder..... is literal.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative ideas:

  • branchBuilder or branchRouteBuilder
  • replaceBuilder or replaceRouteBuilder
  • overrideBuilder or overrideRouteBuilder
  • ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we agree that this approach makes sense, we can discuss the naming in the API review. I think I like the overrideRouteBuilder option the best though.

Copy link
Member

@Tratcher Tratcher Aug 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bool doesn't seem like a meaningful public parameter, anyone that wants the 'true' behavior will already be calling UseRouting().

This should be a separate method name and the bool would be an implementation detail.
UseRoutingAgain() (🚲 🐑)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not make this an extension method and leave it as a plain old static method? I don't expect many people to want to call this overload.

Copy link
Contributor Author

@JunTaoLuo JunTaoLuo Aug 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was actually one of my earlier ideas! I wanted to call it app.ReRoute() or app.RerunRouting() or app.UseRerouting().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of a different name. ResumeRouting?

options.ExceptionHandler = errorBuilder.Build();
}

return new ExceptionHandlerMiddleware(next, loggerFactory, Options.Create(options), diagnosticListener).Invoke;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to change this? Can't you use the normal UseMiddleware<ExceptionHandlerMiddleware>?

@BrennanConroy BrennanConroy changed the title Update ExceptionHandler middleware pipenline construction to reinvoke UseRouting in error branch Update ExceptionHandler middleware pipeline construction to reinvoke UseRouting in error branch Aug 3, 2021
@JunTaoLuo JunTaoLuo force-pushed the johluo/diagnostic-option3 branch from 90450b1 to 596f8b7 Compare August 4, 2021 18:17
@JunTaoLuo JunTaoLuo marked this pull request as ready for review August 4, 2021 18:45
@JunTaoLuo
Copy link
Contributor Author

I'll flip this to a non-draft PR but I expect there to be a few rounds of discussions regarding the design.

Comment on lines 211 to 217
foreach (var item in app.Properties)
{
_builtApplication.Properties[item.Key] = item.Value;
}

// An implicitly created IEndpointRouteBuilder was addeded to app.Properties by the UseRouting() call above.
targetRouteBuilder = GetEndpointRouteBuilder(app)!;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a little less hacky. This works, right?

Suggested change
foreach (var item in app.Properties)
{
_builtApplication.Properties[item.Key] = item.Value;
}
// An implicitly created IEndpointRouteBuilder was addeded to app.Properties by the UseRouting() call above.
targetRouteBuilder = GetEndpointRouteBuilder(app)!;
// An implicitly created IEndpointRouteBuilder was addeded to app.Properties by the UseRouting() call above.
targetRouteBuilder = GetEndpointRouteBuilder(app)!;
// Copy the endpoint route builder to the built application
_builtApplication.Properties[EndpointRouteBuilderKey] = targetRouteBuilder;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd work but I'm curious why that's better? Adding specific item to the Property bag still seems "hacky". I'm curious if we should keep the property bags on _builtApplication and app in sync? Would that be crossing too many wires?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather do only what's necessary rather than do extra stuff. If there was a scenario this fixed, then fine.

@@ -202,7 +205,13 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui
if (targetRouteBuilder is null)
{
// The app defined endpoints without calling UseRouting() explicitly, so call UseRouting() implicitly.
app.UseRouting();
app.UseRouting(overrideEndpointRouteBuilder: false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to call this overload? We know there's no IEndpointRouteBuilder in this case, right?

Copy link
Contributor Author

@JunTaoLuo JunTaoLuo Aug 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite. UseExceptionHandler is added by the user in Program.cs which occurs before app.Run() which is where this configuration runs. So UseRouting() here is configuring an EndpointRoutingMiddleware before the ExceptionHandlerMiddleware in the pipeline but doing so after UseExceptionHandler is called.

Let me know if I need to be clearer since it's all a bit magical.

Copy link
Member

@halter73 halter73 Aug 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why it matters when UseExceptionHandler is called since it's looking for "__WebApplicationBuilder" anyway and that's always defined. The ExceptionHandlerMiddleware will always be added later in the pipeline with the call _builtApplication.BuildRequestDelegate().

Can you provide a test case where there's already an IEndpointRouteBuilder in app.Properties here? It's not easy for code in Program.cs to get access to this app instance. The only thing I could think of is an IStartupFilter or something like that. If we really care about a startup filter calling app.UseRouting() before calling next(app), we could skip calling UseRouting() altogether. I'm not convinced that's realistic though, so I'm not sure it's worth complicating the code for for this. If we do, we should add a regression test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm now I think about it, I don't recall why I set the argument to false here. However, I do clearly recall without it, things didn't work. I could debug it to come up with a more clear explanation.

That being said, after chatting with @javiercn today, I'm not sure if this is the approach we are going to take so I think it's a moot point. I think there's more investigation/design to be done before addressing this specific feedback.

@@ -170,6 +171,8 @@ public WebApplication Build()
((IConfigurationBuilder)Configuration).Sources.Clear();
Configuration.AddConfiguration(_builtApplication.Configuration);

_builtApplication.Properties[WebApplicationBuilderKey] = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of this property? Trying to grok where it is used...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind! I see you are using this in the extension methods to check if they are invoked on a WebApplicationBuilder. Correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I added this "hack" to clearly indicate that the builder is a WebApplicationBuilder, where we make use of "creative" pipeline construction techniques 😄

@Kahbazi
Copy link
Member

Kahbazi commented Aug 6, 2021

This method also needs to be updated.

public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware<ExceptionHandlerMiddleware>();
}

for the case when ExceptionHandlerOptions is configured via service collection.

public static IServiceCollection AddExceptionHandler(this IServiceCollection services, Action<ExceptionHandlerOptions> configureOptions)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}
return services.Configure(configureOptions);
}

@BrennanConroy
Copy link
Member

Closing in favor of #35426

@BrennanConroy BrennanConroy deleted the johluo/diagnostic-option3 branch August 17, 2021 23:08
@amcasey amcasey added area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares and removed area-runtime labels Jun 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implicit call to UseRouting() in WebApplicationBuilder creates ordering issue with pipeline rerunning middleware, e.g. UseExceptionHandler
9 participants