Skip to content

Commit 81e3824

Browse files
authored
Capture Endpoint and RouteValues on ExceptionHandlerFeature (#34205)
1 parent cdb95eb commit 81e3824

File tree

7 files changed

+68
-5
lines changed

7 files changed

+68
-5
lines changed

src/Middleware/Diagnostics.Abstractions/src/IExceptionHandlerFeature.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Routing;
57

68
namespace Microsoft.AspNetCore.Diagnostics
79
{
@@ -14,5 +16,21 @@ public interface IExceptionHandlerFeature
1416
/// The error encountered during the original request
1517
/// </summary>
1618
Exception Error { get; }
19+
20+
/// <summary>
21+
/// The portion of the request path that identifies the requested resource. The value
22+
/// is un-escaped.
23+
/// </summary>
24+
string Path => throw new NotSupportedException();
25+
26+
/// <summary>
27+
/// Gets the selected <see cref="Http.Endpoint"/> for the original request.
28+
/// </summary>
29+
Endpoint? Endpoint => null;
30+
31+
/// <summary>
32+
/// Gets the <see cref="RouteValueDictionary"/> associated with the original request.
33+
/// </summary>
34+
RouteValueDictionary? RouteValues => null;
1735
}
1836
}

src/Middleware/Diagnostics.Abstractions/src/IExceptionHandlerPathFeature.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ public interface IExceptionHandlerPathFeature : IExceptionHandlerFeature
1212
/// The portion of the request path that identifies the requested resource. The value
1313
/// is un-escaped.
1414
/// </summary>
15-
string? Path { get; }
15+
new string Path => ((IExceptionHandlerFeature)this).Path;
1616
}
1717
}

src/Middleware/Diagnostics.Abstractions/src/PublicAPI.Unshipped.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ Microsoft.AspNetCore.Diagnostics.ErrorContext.Exception.get -> System.Exception!
1515
Microsoft.AspNetCore.Diagnostics.ErrorContext.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext!
1616
Microsoft.AspNetCore.Diagnostics.ICompilationException.CompilationFailures.get -> System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Diagnostics.CompilationFailure?>?
1717
Microsoft.AspNetCore.Diagnostics.IDeveloperPageExceptionFilter.HandleExceptionAsync(Microsoft.AspNetCore.Diagnostics.ErrorContext! errorContext, System.Func<Microsoft.AspNetCore.Diagnostics.ErrorContext!, System.Threading.Tasks.Task!>! next) -> System.Threading.Tasks.Task!
18+
Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature.Endpoint.get -> Microsoft.AspNetCore.Http.Endpoint?
1819
Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature.Error.get -> System.Exception!
19-
Microsoft.AspNetCore.Diagnostics.IExceptionHandlerPathFeature.Path.get -> string?
20+
Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature.Path.get -> string!
21+
Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature.RouteValues.get -> Microsoft.AspNetCore.Routing.RouteValueDictionary?
22+
Microsoft.AspNetCore.Diagnostics.IExceptionHandlerPathFeature.Path.get -> string!
2023
Microsoft.AspNetCore.Diagnostics.IStatusCodeReExecuteFeature.OriginalPath.get -> string!
2124
Microsoft.AspNetCore.Diagnostics.IStatusCodeReExecuteFeature.OriginalPath.set -> void
2225
Microsoft.AspNetCore.Diagnostics.IStatusCodeReExecuteFeature.OriginalPathBase.get -> string!

src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerFeature.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Routing;
57

68
namespace Microsoft.AspNetCore.Diagnostics
79
{
@@ -15,5 +17,11 @@ public class ExceptionHandlerFeature : IExceptionHandlerPathFeature
1517

1618
/// <inheritdoc/>
1719
public string Path { get; set; } = default!;
20+
21+
/// <inheritdoc/>
22+
public Endpoint? Endpoint { get; set; }
23+
24+
/// <inheritdoc/>
25+
public RouteValueDictionary? RouteValues { get; set; }
1826
}
1927
}

src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,16 @@ private async Task HandleException(HttpContext context, ExceptionDispatchInfo ed
118118
}
119119
try
120120
{
121-
ClearHttpContext(context);
122-
123121
var exceptionHandlerFeature = new ExceptionHandlerFeature()
124122
{
125123
Error = edi.SourceException,
126124
Path = originalPath.Value!,
125+
Endpoint = context.GetEndpoint(),
126+
RouteValues = context.Features.Get<IRouteValuesFeature>()?.RouteValues
127127
};
128+
129+
ClearHttpContext(context);
130+
128131
context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
129132
context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
130133
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
@@ -168,7 +171,10 @@ private static void ClearHttpContext(HttpContext context)
168171
// the endpoint and route values to ensure things are re-calculated.
169172
context.SetEndpoint(endpoint: null);
170173
var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
171-
routeValuesFeature?.RouteValues?.Clear();
174+
if (routeValuesFeature != null)
175+
{
176+
routeValuesFeature.RouteValues = null!;
177+
}
172178
}
173179

174180
private static Task ClearCacheHeaders(object state)

src/Middleware/Diagnostics/src/PublicAPI.Unshipped.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ Microsoft.AspNetCore.Builder.ExceptionHandlerOptions.ExceptionHandler.get -> Mic
5252
Microsoft.AspNetCore.Builder.ExceptionHandlerOptions.ExceptionHandler.set -> void
5353
Microsoft.AspNetCore.Builder.StatusCodePagesOptions.HandleAsync.get -> System.Func<Microsoft.AspNetCore.Diagnostics.StatusCodeContext!, System.Threading.Tasks.Task!>!
5454
Microsoft.AspNetCore.Builder.StatusCodePagesOptions.HandleAsync.set -> void
55+
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerFeature.Endpoint.get -> Microsoft.AspNetCore.Http.Endpoint?
56+
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerFeature.Endpoint.set -> void
57+
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerFeature.RouteValues.get -> Microsoft.AspNetCore.Routing.RouteValueDictionary?
58+
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerFeature.RouteValues.set -> void
5559
~Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.DeveloperExceptionPageMiddleware(Microsoft.AspNetCore.Http.RequestDelegate! next, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Builder.DeveloperExceptionPageOptions!>! options, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory, Microsoft.AspNetCore.Hosting.IWebHostEnvironment! hostingEnvironment, System.Diagnostics.DiagnosticSource! diagnosticSource, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Diagnostics.IDeveloperPageExceptionFilter!>! filters) -> void
5660
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext! context) -> System.Threading.Tasks.Task!
5761
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerFeature.Error.get -> System.Exception!

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,30 @@ public async Task Invoke_ExceptionThrownResultsInClearedRouteValuesAndEndpoint()
3636
await middleware.Invoke(httpContext);
3737
}
3838

39+
[Fact]
40+
public async Task Invoke_ExceptionHandlerCaptureRouteValuesAndEndpoint()
41+
{
42+
// Arrange
43+
var httpContext = CreateHttpContext();
44+
var endpoint = new Endpoint((_) => Task.CompletedTask, new EndpointMetadataCollection(), "Test");
45+
httpContext.SetEndpoint(endpoint);
46+
httpContext.Request.RouteValues["John"] = "Doe";
47+
48+
var optionsAccessor = CreateOptionsAccessor(
49+
exceptionHandler: context =>
50+
{
51+
var feature = context.Features.Get<IExceptionHandlerPathFeature>();
52+
Assert.Equal(endpoint, feature.Endpoint);
53+
Assert.Equal("Doe", feature.RouteValues["John"]);
54+
55+
return Task.CompletedTask;
56+
});
57+
var middleware = CreateMiddleware(_ => throw new InvalidOperationException(), optionsAccessor);
58+
59+
// Act & Assert
60+
await middleware.Invoke(httpContext);
61+
}
62+
3963
private HttpContext CreateHttpContext()
4064
{
4165
var httpContext = new DefaultHttpContext

0 commit comments

Comments
 (0)