From 5d82f4b0d9cc0202e4341c6b2bfdb1af54e5cbe8 Mon Sep 17 00:00:00 2001 From: bjornen77 <58444816+bjornen77@users.noreply.github.com> Date: Fri, 31 Mar 2023 22:36:22 +0200 Subject: [PATCH 1/7] Set exception handler feature in developer exception page When using the developer exception page, the exception handler feature is now set before invoking the problem details service. This makes it possible to get the original exception when using the problem details service if wanted. Fixes #47060 --- .../DeveloperExceptionPageMiddlewareImpl.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs b/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs index d6f51b961633..b8c3b202db3a 100644 --- a/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs +++ b/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs @@ -88,6 +88,22 @@ private static ExtensionsExceptionJsonContext CreateSerializationContext(JsonOpt return new ExtensionsExceptionJsonContext(new JsonSerializerOptions(jsonOptions.SerializerOptions)); } + private static void SetExceptionHandlerFeatures(ErrorContext errorContext) + { + var httpContext = errorContext.HttpContext; + + ExceptionHandlerFeature exceptionHandlerFeature = new() + { + Error = errorContext.Exception, + Path = httpContext.Request.Path.ToString(), + Endpoint = httpContext.GetEndpoint(), + RouteValues = httpContext.Features.Get()?.RouteValues + }; + + httpContext.Features.Set(exceptionHandlerFeature); + httpContext.Features.Set(exceptionHandlerFeature); + } + /// /// Process an individual request. /// @@ -184,6 +200,11 @@ private async Task DisplayExceptionContent(ErrorContext errorContext) { var httpContext = errorContext.HttpContext; + if (_problemDetailsService is not null) + { + SetExceptionHandlerFeatures(errorContext); + } + if (_problemDetailsService == null || !await _problemDetailsService.TryWriteAsync(new() { HttpContext = httpContext, ProblemDetails = CreateProblemDetails(errorContext, httpContext) })) { From dd822a86fefdd5f32700da9b69f99b869c788282 Mon Sep 17 00:00:00 2001 From: bjornen77 <58444816+bjornen77@users.noreply.github.com> Date: Mon, 3 Apr 2023 22:24:51 +0200 Subject: [PATCH 2/7] add test --- .../DeveloperExceptionPageMiddlewareTest.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs b/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs index f464913004a1..bffb1f47f8d7 100644 --- a/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs +++ b/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs @@ -2,10 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Net.Http; using System.Net.Http.Headers; +using System.Net.Http.Json; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -14,6 +17,54 @@ namespace Microsoft.AspNetCore.Diagnostics; public class DeveloperExceptionPageMiddlewareTest { + [Fact] + public async Task ExceptionHandlerFeatureIsAvailableInCustomizeProblemDetailsWhenUsingExceptionPage() + { + // Arrange + using var host = new HostBuilder() + .ConfigureServices(services => + { + services.AddProblemDetails(configure => + { + configure.CustomizeProblemDetails = (context) => + { + var feature = context.HttpContext.Features.Get(); + context.ProblemDetails.Extensions.Add("originalExceptionMessage", feature?.Error.Message); + }; + + }); + }) + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseTestServer() + .Configure(app => + { + app.UseDeveloperExceptionPage(); + app.Run(context => + { + throw new Exception("Test exception"); + }); + }); + }).Build(); + + await host.StartAsync(); + + var server = host.GetTestServer(); + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/"); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + // Act + await server.CreateClient().GetAsync("/path"); + var response = await server.CreateClient().SendAsync(request); + + + // Assert + var body = await response.Content.ReadFromJsonAsync(); + var originalExceptionMessage = (string)body.Extensions["originalExceptionMessage"]; + Assert.Equal("Test exception", originalExceptionMessage); + } + [Fact] public async Task UnhandledErrorsWriteToDiagnosticWhenUsingExceptionPage() { From 2d7af7e62970951c4c3f265b816c7edad74f6414 Mon Sep 17 00:00:00 2001 From: bjornen77 <58444816+bjornen77@users.noreply.github.com> Date: Mon, 3 Apr 2023 20:41:14 +0000 Subject: [PATCH 3/7] fix test --- .../test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs b/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs index bffb1f47f8d7..7760b1784f73 100644 --- a/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs +++ b/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; +using System.Text.Json; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -58,10 +59,9 @@ public async Task ExceptionHandlerFeatureIsAvailableInCustomizeProblemDetailsWhe await server.CreateClient().GetAsync("/path"); var response = await server.CreateClient().SendAsync(request); - // Assert var body = await response.Content.ReadFromJsonAsync(); - var originalExceptionMessage = (string)body.Extensions["originalExceptionMessage"]; + var originalExceptionMessage = ((JsonElement)body.Extensions["originalExceptionMessage"]).GetString(); Assert.Equal("Test exception", originalExceptionMessage); } From daf4b47b899d320241ac3dc985dfbca6d680c283 Mon Sep 17 00:00:00 2001 From: bjornen77 <58444816+bjornen77@users.noreply.github.com> Date: Tue, 4 Apr 2023 12:29:41 +0000 Subject: [PATCH 4/7] extend test with more asserts --- .../DeveloperExceptionPageMiddlewareTest.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs b/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs index 7760b1784f73..5cda8f699a7d 100644 --- a/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs +++ b/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs @@ -25,12 +25,16 @@ public async Task ExceptionHandlerFeatureIsAvailableInCustomizeProblemDetailsWhe using var host = new HostBuilder() .ConfigureServices(services => { + services.AddRouting(); services.AddProblemDetails(configure => { configure.CustomizeProblemDetails = (context) => { var feature = context.HttpContext.Features.Get(); - context.ProblemDetails.Extensions.Add("originalExceptionMessage", feature?.Error.Message); + context.ProblemDetails.Extensions.Add("OriginalExceptionMessage", feature?.Error.Message); + context.ProblemDetails.Extensions.Add("EndpointDisplayName", feature?.Endpoint?.DisplayName); + context.ProblemDetails.Extensions.Add("RouteValue", feature?.RouteValues?["id"]); + context.ProblemDetails.Extensions.Add("Path", feature?.Path); }; }); @@ -42,9 +46,13 @@ public async Task ExceptionHandlerFeatureIsAvailableInCustomizeProblemDetailsWhe .Configure(app => { app.UseDeveloperExceptionPage(); - app.Run(context => + app.UseRouting(); + app.UseEndpoints(endpoint => { - throw new Exception("Test exception"); + endpoint.MapGet("/test/{id}", (int id) => + { + throw new Exception("Test exception"); + }); }); }); }).Build(); @@ -52,17 +60,22 @@ public async Task ExceptionHandlerFeatureIsAvailableInCustomizeProblemDetailsWhe await host.StartAsync(); var server = host.GetTestServer(); - var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/"); + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/test/1"); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // Act - await server.CreateClient().GetAsync("/path"); var response = await server.CreateClient().SendAsync(request); // Assert var body = await response.Content.ReadFromJsonAsync(); - var originalExceptionMessage = ((JsonElement)body.Extensions["originalExceptionMessage"]).GetString(); + var originalExceptionMessage = ((JsonElement)body.Extensions["OriginalExceptionMessage"]).GetString(); + var endpointDisplayName = ((JsonElement)body.Extensions["EndpointDisplayName"]).GetString(); + var routeValue = ((JsonElement)body.Extensions["RouteValue"]).GetString(); + var path = ((JsonElement)body.Extensions["Path"]).GetString(); Assert.Equal("Test exception", originalExceptionMessage); + Assert.Contains("/test/{id}", endpointDisplayName); + Assert.Equal("1", routeValue); + Assert.Equal("/test/1", path); } [Fact] From a3b743ca6799f5e530cdfe5ee5988c0a3f34b58a Mon Sep 17 00:00:00 2001 From: bjornen77 <58444816+bjornen77@users.noreply.github.com> Date: Tue, 4 Apr 2023 14:32:42 +0200 Subject: [PATCH 5/7] add test for exceptionhandlerpathfeature --- .../DeveloperExceptionPageMiddlewareTest.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs b/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs index 5cda8f699a7d..4c6740ab368e 100644 --- a/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs +++ b/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs @@ -78,6 +78,66 @@ public async Task ExceptionHandlerFeatureIsAvailableInCustomizeProblemDetailsWhe Assert.Equal("/test/1", path); } + [Fact] + public async Task ExceptionHandlerPathFeatureIsAvailableInCustomizeProblemDetailsWhenUsingExceptionPage() + { + // Arrange + using var host = new HostBuilder() + .ConfigureServices(services => + { + services.AddRouting(); + services.AddProblemDetails(configure => + { + configure.CustomizeProblemDetails = (context) => + { + var feature = context.HttpContext.Features.Get(); + context.ProblemDetails.Extensions.Add("OriginalExceptionMessage", feature?.Error.Message); + context.ProblemDetails.Extensions.Add("EndpointDisplayName", feature?.Endpoint?.DisplayName); + context.ProblemDetails.Extensions.Add("RouteValue", feature?.RouteValues?["id"]); + context.ProblemDetails.Extensions.Add("Path", feature?.Path); + }; + + }); + }) + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseTestServer() + .Configure(app => + { + app.UseDeveloperExceptionPage(); + app.UseRouting(); + app.UseEndpoints(endpoint => + { + endpoint.MapGet("/test/{id}", (int id) => + { + throw new Exception("Test exception"); + }); + }); + }); + }).Build(); + + await host.StartAsync(); + + var server = host.GetTestServer(); + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/test/1"); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + // Act + var response = await server.CreateClient().SendAsync(request); + + // Assert + var body = await response.Content.ReadFromJsonAsync(); + var originalExceptionMessage = ((JsonElement)body.Extensions["OriginalExceptionMessage"]).GetString(); + var endpointDisplayName = ((JsonElement)body.Extensions["EndpointDisplayName"]).GetString(); + var routeValue = ((JsonElement)body.Extensions["RouteValue"]).GetString(); + var path = ((JsonElement)body.Extensions["Path"]).GetString(); + Assert.Equal("Test exception", originalExceptionMessage); + Assert.Contains("/test/{id}", endpointDisplayName); + Assert.Equal("1", routeValue); + Assert.Equal("/test/1", path); + } + [Fact] public async Task UnhandledErrorsWriteToDiagnosticWhenUsingExceptionPage() { From b694767c426aba47e1cf613a429b55c2b2793ad0 Mon Sep 17 00:00:00 2001 From: bjornen77 <58444816+bjornen77@users.noreply.github.com> Date: Tue, 4 Apr 2023 12:38:50 +0000 Subject: [PATCH 6/7] minor cleanup --- .../test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs b/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs index 4c6740ab368e..7ae5afb0d2c2 100644 --- a/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs +++ b/src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs @@ -36,7 +36,6 @@ public async Task ExceptionHandlerFeatureIsAvailableInCustomizeProblemDetailsWhe context.ProblemDetails.Extensions.Add("RouteValue", feature?.RouteValues?["id"]); context.ProblemDetails.Extensions.Add("Path", feature?.Path); }; - }); }) .ConfigureWebHost(webHostBuilder => @@ -96,7 +95,6 @@ public async Task ExceptionHandlerPathFeatureIsAvailableInCustomizeProblemDetail context.ProblemDetails.Extensions.Add("RouteValue", feature?.RouteValues?["id"]); context.ProblemDetails.Extensions.Add("Path", feature?.Path); }; - }); }) .ConfigureWebHost(webHostBuilder => From bd215d1e2b7743175dcc7731d644a234d1dde4b4 Mon Sep 17 00:00:00 2001 From: bjornen77 <58444816+bjornen77@users.noreply.github.com> Date: Mon, 10 Apr 2023 08:17:25 +0200 Subject: [PATCH 7/7] fix PR feedback --- .../DeveloperExceptionPageMiddlewareImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs b/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs index b8c3b202db3a..44c489e27cc6 100644 --- a/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs +++ b/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs @@ -92,7 +92,7 @@ private static void SetExceptionHandlerFeatures(ErrorContext errorContext) { var httpContext = errorContext.HttpContext; - ExceptionHandlerFeature exceptionHandlerFeature = new() + var exceptionHandlerFeature = new ExceptionHandlerFeature() { Error = errorContext.Exception, Path = httpContext.Request.Path.ToString(),