diff --git a/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs b/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs index d954e00a44a2..7be8b56869c2 100644 --- a/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs +++ b/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs @@ -132,7 +132,7 @@ private async Task HandleException(HttpContext context, ExceptionDispatchInfo ed await _options.ExceptionHandler(context); - if (context.Response.StatusCode != StatusCodes.Status404NotFound) + if (context.Response.StatusCode != StatusCodes.Status404NotFound || _options.AllowStatusCode404Response) { if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled("Microsoft.AspNetCore.Diagnostics.HandledException")) { diff --git a/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerOptions.cs b/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerOptions.cs index e5971ecd777d..370c6e419d60 100644 --- a/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerOptions.cs +++ b/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerOptions.cs @@ -22,5 +22,14 @@ public class ExceptionHandlerOptions /// explicitly provided, the subsequent middleware pipeline will be used by default. /// public RequestDelegate ExceptionHandler { get; set; } + + /// + /// This value controls whether the should + /// consider a response with a 404 status code to be a valid result of executing the + /// . The default value is false and the middleware will + /// consider 404 status codes to be an error on the server and will therefore rethrow + /// the original exception. + /// + public bool AllowStatusCode404Response { get; set; } } } diff --git a/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs b/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs index 1b8df0af5344..8c33d4a8a1e3 100644 --- a/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs +++ b/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs @@ -542,5 +542,59 @@ public async Task ExceptionHandlerNotFound_RethrowsOriginalError() && w.EventId == 4 && w.Message == "No exception handler was found, rethrowing original exception."); } + + [Fact] + public async Task ExceptionHandler_CanReturn404Responses_WhenAllowed() + { + var sink = new TestSink(TestSink.EnableWithTypeName); + var loggerFactory = new TestLoggerFactory(sink, enabled: true); + + using var host = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseTestServer() + .ConfigureServices(services => + { + services.AddSingleton(loggerFactory); + services.Configure(options => + { + options.AllowStatusCode404Response = true; + options.ExceptionHandler = httpContext => + { + httpContext.Response.StatusCode = StatusCodes.Status404NotFound; + return Task.CompletedTask; + }; + }); + }) + .Configure(app => + { + app.UseExceptionHandler(); + + app.Map("/throw", (innerAppBuilder) => + { + innerAppBuilder.Run(httpContext => + { + throw new InvalidOperationException("Something bad happened."); + }); + }); + }); + }).Build(); + + await host.StartAsync(); + + using (var server = host.GetTestServer()) + { + var client = server.CreateClient(); + var response = await client.GetAsync("throw"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + Assert.Equal(string.Empty, await response.Content.ReadAsStringAsync()); + } + + Assert.DoesNotContain(sink.Writes, w => + w.LogLevel == LogLevel.Warning + && w.EventId == 4 + && w.Message == "No exception handler was found, rethrowing original exception."); + } } }