Skip to content

Commit f6452bf

Browse files
Update middleware that assumes UseRouting is called after them, for minimal hosting (#35426)
1 parent 22b077e commit f6452bf

11 files changed

+559
-9
lines changed

src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerExtensions.cs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
4+
using System.Diagnostics;
55
using Microsoft.AspNetCore.Diagnostics;
66
using Microsoft.AspNetCore.Http;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Logging;
79
using Microsoft.Extensions.Options;
810

911
namespace Microsoft.AspNetCore.Builder
@@ -26,7 +28,7 @@ public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder a
2628
throw new ArgumentNullException(nameof(app));
2729
}
2830

29-
return app.UseMiddleware<ExceptionHandlerMiddleware>();
31+
return SetExceptionHandlerMiddleware(app, options: null);
3032
}
3133

3234
/// <summary>
@@ -95,7 +97,50 @@ public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder a
9597
throw new ArgumentNullException(nameof(options));
9698
}
9799

98-
return app.UseMiddleware<ExceptionHandlerMiddleware>(Options.Create(options));
100+
var iOptions = Options.Create(options);
101+
return SetExceptionHandlerMiddleware(app, iOptions);
102+
}
103+
104+
private static IApplicationBuilder SetExceptionHandlerMiddleware(IApplicationBuilder app, IOptions<ExceptionHandlerOptions>? options)
105+
{
106+
const string globalRouteBuilderKey = "__GlobalEndpointRouteBuilder";
107+
// Only use this path if there's a global router (in the 'WebApplication' case).
108+
if (app.Properties.TryGetValue(globalRouteBuilderKey, out var routeBuilder) && routeBuilder is not null)
109+
{
110+
return app.Use(next =>
111+
{
112+
var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
113+
var diagnosticListener = app.ApplicationServices.GetRequiredService<DiagnosticListener>();
114+
115+
if (options is null)
116+
{
117+
options = app.ApplicationServices.GetRequiredService<IOptions<ExceptionHandlerOptions>>();
118+
}
119+
120+
if (!string.IsNullOrEmpty(options.Value.ExceptionHandlingPath) && options.Value.ExceptionHandler is null)
121+
{
122+
// start a new middleware pipeline
123+
var builder = app.New();
124+
// use the old routing pipeline if it exists so we preserve all the routes and matching logic
125+
// ((IApplicationBuilder)WebApplication).New() does not copy globalRouteBuilderKey automatically like it does for all other properties.
126+
builder.Properties[globalRouteBuilderKey] = routeBuilder;
127+
builder.UseRouting();
128+
// apply the next middleware
129+
builder.Run(next);
130+
// store the pipeline for the error case
131+
options.Value.ExceptionHandler = builder.Build();
132+
}
133+
134+
return new ExceptionHandlerMiddleware(next, loggerFactory, options, diagnosticListener).Invoke;
135+
});
136+
}
137+
138+
if (options is null)
139+
{
140+
return app.UseMiddleware<ExceptionHandlerMiddleware>();
141+
}
142+
143+
return app.UseMiddleware<ExceptionHandlerMiddleware>(options);
99144
}
100145
}
101146
}

src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesExtensions.cs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,34 @@ public static IApplicationBuilder UseStatusCodePagesWithReExecute(
174174
throw new ArgumentNullException(nameof(app));
175175
}
176176

177-
return app.UseStatusCodePages(async context =>
177+
const string globalRouteBuilderKey = "__GlobalEndpointRouteBuilder";
178+
// Only use this path if there's a global router (in the 'WebApplication' case).
179+
if (app.Properties.TryGetValue(globalRouteBuilderKey, out var routeBuilder) && routeBuilder is not null)
180+
{
181+
return app.Use(next =>
182+
{
183+
RequestDelegate? newNext = null;
184+
// start a new middleware pipeline
185+
var builder = app.New();
186+
// use the old routing pipeline if it exists so we preserve all the routes and matching logic
187+
// ((IApplicationBuilder)WebApplication).New() does not copy globalRouteBuilderKey automatically like it does for all other properties.
188+
builder.Properties[globalRouteBuilderKey] = routeBuilder;
189+
builder.UseRouting();
190+
// apply the next middleware
191+
builder.Run(next);
192+
newNext = builder.Build();
193+
194+
return new StatusCodePagesMiddleware(next,
195+
Options.Create(new StatusCodePagesOptions() { HandleAsync = CreateHandler(pathFormat, queryFormat, newNext) })).Invoke;
196+
});
197+
}
198+
199+
return app.UseStatusCodePages(CreateHandler(pathFormat, queryFormat));
200+
}
201+
202+
private static Func<StatusCodeContext, Task> CreateHandler(string pathFormat, string? queryFormat, RequestDelegate? next = null)
203+
{
204+
var handler = async (StatusCodeContext context) =>
178205
{
179206
var newPath = new PathString(
180207
string.Format(CultureInfo.InvariantCulture, pathFormat, context.HttpContext.Response.StatusCode));
@@ -202,15 +229,24 @@ public static IApplicationBuilder UseStatusCodePagesWithReExecute(
202229
context.HttpContext.Request.QueryString = newQueryString;
203230
try
204231
{
205-
await context.Next(context.HttpContext);
232+
if (next is not null)
233+
{
234+
await next(context.HttpContext);
235+
}
236+
else
237+
{
238+
await context.Next(context.HttpContext);
239+
}
206240
}
207241
finally
208242
{
209243
context.HttpContext.Request.QueryString = originalQueryString;
210244
context.HttpContext.Request.Path = originalPath;
211245
context.HttpContext.Features.Set<IStatusCodeReExecuteFeature?>(null);
212246
}
213-
});
247+
};
248+
249+
return handler;
214250
}
215251
}
216252
}

0 commit comments

Comments
 (0)