Skip to content

Commit 0f65d69

Browse files
authored
Set exception handler feature in developer exception page (#47554)
* 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 * add test * fix test * extend test with more asserts * add test for exceptionhandlerpathfeature * minor cleanup * fix PR feedback
1 parent 63642be commit 0f65d69

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,22 @@ private static ExtensionsExceptionJsonContext CreateSerializationContext(JsonOpt
8888
return new ExtensionsExceptionJsonContext(new JsonSerializerOptions(jsonOptions.SerializerOptions));
8989
}
9090

91+
private static void SetExceptionHandlerFeatures(ErrorContext errorContext)
92+
{
93+
var httpContext = errorContext.HttpContext;
94+
95+
var exceptionHandlerFeature = new ExceptionHandlerFeature()
96+
{
97+
Error = errorContext.Exception,
98+
Path = httpContext.Request.Path.ToString(),
99+
Endpoint = httpContext.GetEndpoint(),
100+
RouteValues = httpContext.Features.Get<IRouteValuesFeature>()?.RouteValues
101+
};
102+
103+
httpContext.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
104+
httpContext.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
105+
}
106+
91107
/// <summary>
92108
/// Process an individual request.
93109
/// </summary>
@@ -184,6 +200,11 @@ private async Task DisplayExceptionContent(ErrorContext errorContext)
184200
{
185201
var httpContext = errorContext.HttpContext;
186202

203+
if (_problemDetailsService is not null)
204+
{
205+
SetExceptionHandlerFeatures(errorContext);
206+
}
207+
187208
if (_problemDetailsService == null ||
188209
!await _problemDetailsService.TryWriteAsync(new() { HttpContext = httpContext, ProblemDetails = CreateProblemDetails(errorContext, httpContext) }))
189210
{

src/Middleware/Diagnostics/test/UnitTests/DeveloperExceptionPageMiddlewareTest.cs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5+
using System.Net.Http;
56
using System.Net.Http.Headers;
7+
using System.Net.Http.Json;
8+
using System.Text.Json;
69
using Microsoft.AspNetCore.Builder;
710
using Microsoft.AspNetCore.Hosting;
811
using Microsoft.AspNetCore.Http;
12+
using Microsoft.AspNetCore.Mvc;
913
using Microsoft.AspNetCore.TestHost;
1014
using Microsoft.Extensions.DependencyInjection;
1115
using Microsoft.Extensions.Hosting;
@@ -14,6 +18,124 @@ namespace Microsoft.AspNetCore.Diagnostics;
1418

1519
public class DeveloperExceptionPageMiddlewareTest
1620
{
21+
[Fact]
22+
public async Task ExceptionHandlerFeatureIsAvailableInCustomizeProblemDetailsWhenUsingExceptionPage()
23+
{
24+
// Arrange
25+
using var host = new HostBuilder()
26+
.ConfigureServices(services =>
27+
{
28+
services.AddRouting();
29+
services.AddProblemDetails(configure =>
30+
{
31+
configure.CustomizeProblemDetails = (context) =>
32+
{
33+
var feature = context.HttpContext.Features.Get<IExceptionHandlerFeature>();
34+
context.ProblemDetails.Extensions.Add("OriginalExceptionMessage", feature?.Error.Message);
35+
context.ProblemDetails.Extensions.Add("EndpointDisplayName", feature?.Endpoint?.DisplayName);
36+
context.ProblemDetails.Extensions.Add("RouteValue", feature?.RouteValues?["id"]);
37+
context.ProblemDetails.Extensions.Add("Path", feature?.Path);
38+
};
39+
});
40+
})
41+
.ConfigureWebHost(webHostBuilder =>
42+
{
43+
webHostBuilder
44+
.UseTestServer()
45+
.Configure(app =>
46+
{
47+
app.UseDeveloperExceptionPage();
48+
app.UseRouting();
49+
app.UseEndpoints(endpoint =>
50+
{
51+
endpoint.MapGet("/test/{id}", (int id) =>
52+
{
53+
throw new Exception("Test exception");
54+
});
55+
});
56+
});
57+
}).Build();
58+
59+
await host.StartAsync();
60+
61+
var server = host.GetTestServer();
62+
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/test/1");
63+
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
64+
65+
// Act
66+
var response = await server.CreateClient().SendAsync(request);
67+
68+
// Assert
69+
var body = await response.Content.ReadFromJsonAsync<ProblemDetails>();
70+
var originalExceptionMessage = ((JsonElement)body.Extensions["OriginalExceptionMessage"]).GetString();
71+
var endpointDisplayName = ((JsonElement)body.Extensions["EndpointDisplayName"]).GetString();
72+
var routeValue = ((JsonElement)body.Extensions["RouteValue"]).GetString();
73+
var path = ((JsonElement)body.Extensions["Path"]).GetString();
74+
Assert.Equal("Test exception", originalExceptionMessage);
75+
Assert.Contains("/test/{id}", endpointDisplayName);
76+
Assert.Equal("1", routeValue);
77+
Assert.Equal("/test/1", path);
78+
}
79+
80+
[Fact]
81+
public async Task ExceptionHandlerPathFeatureIsAvailableInCustomizeProblemDetailsWhenUsingExceptionPage()
82+
{
83+
// Arrange
84+
using var host = new HostBuilder()
85+
.ConfigureServices(services =>
86+
{
87+
services.AddRouting();
88+
services.AddProblemDetails(configure =>
89+
{
90+
configure.CustomizeProblemDetails = (context) =>
91+
{
92+
var feature = context.HttpContext.Features.Get<IExceptionHandlerPathFeature>();
93+
context.ProblemDetails.Extensions.Add("OriginalExceptionMessage", feature?.Error.Message);
94+
context.ProblemDetails.Extensions.Add("EndpointDisplayName", feature?.Endpoint?.DisplayName);
95+
context.ProblemDetails.Extensions.Add("RouteValue", feature?.RouteValues?["id"]);
96+
context.ProblemDetails.Extensions.Add("Path", feature?.Path);
97+
};
98+
});
99+
})
100+
.ConfigureWebHost(webHostBuilder =>
101+
{
102+
webHostBuilder
103+
.UseTestServer()
104+
.Configure(app =>
105+
{
106+
app.UseDeveloperExceptionPage();
107+
app.UseRouting();
108+
app.UseEndpoints(endpoint =>
109+
{
110+
endpoint.MapGet("/test/{id}", (int id) =>
111+
{
112+
throw new Exception("Test exception");
113+
});
114+
});
115+
});
116+
}).Build();
117+
118+
await host.StartAsync();
119+
120+
var server = host.GetTestServer();
121+
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/test/1");
122+
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
123+
124+
// Act
125+
var response = await server.CreateClient().SendAsync(request);
126+
127+
// Assert
128+
var body = await response.Content.ReadFromJsonAsync<ProblemDetails>();
129+
var originalExceptionMessage = ((JsonElement)body.Extensions["OriginalExceptionMessage"]).GetString();
130+
var endpointDisplayName = ((JsonElement)body.Extensions["EndpointDisplayName"]).GetString();
131+
var routeValue = ((JsonElement)body.Extensions["RouteValue"]).GetString();
132+
var path = ((JsonElement)body.Extensions["Path"]).GetString();
133+
Assert.Equal("Test exception", originalExceptionMessage);
134+
Assert.Contains("/test/{id}", endpointDisplayName);
135+
Assert.Equal("1", routeValue);
136+
Assert.Equal("/test/1", path);
137+
}
138+
17139
[Fact]
18140
public async Task UnhandledErrorsWriteToDiagnosticWhenUsingExceptionPage()
19141
{

0 commit comments

Comments
 (0)