Ver código fonte

Reset endpoint and route values during exception handling.

- We initially did this change as part of EndpointRouting but the impact of that change resulted in a variety of performance regressions. To mitigate the impact of resetting state for a request we now only reset the state when an exception has occurred in a way that does not require any additional state machines to be allocated.
- Added a test to validate that http context state gets reset on exception handling.

#12897
N. Taylor Mullen 6 anos atrás
pai
commit
1f0641f5c0

+ 14 - 1
src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs

@@ -7,6 +7,7 @@ using System.Runtime.ExceptionServices;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using Microsoft.Net.Http.Headers;
@@ -103,7 +104,8 @@ namespace Microsoft.AspNetCore.Diagnostics
             }
             try
             {
-                context.Response.Clear();
+                ClearHttpContext(context);
+
                 var exceptionHandlerFeature = new ExceptionHandlerFeature()
                 {
                     Error = edi.SourceException,
@@ -137,6 +139,17 @@ namespace Microsoft.AspNetCore.Diagnostics
             edi.Throw(); // Re-throw the original if we couldn't handle it
         }
 
+        private static void ClearHttpContext(HttpContext context)
+        {
+            context.Response.Clear();
+
+            // An endpoint may have already been set. Since we're going to re-invoke the middleware pipeline we need to reset
+            // the endpoint and route values to ensure things are re-calculated.
+            context.SetEndpoint(endpoint: null);
+            var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
+            routeValuesFeature?.RouteValues?.Clear();
+        }
+
         private static Task ClearCacheHeaders(object state)
         {
             var headers = ((HttpResponse)state).Headers;

+ 87 - 0
src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerMiddlewareTest.cs

@@ -0,0 +1,87 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Diagnostics
+{
+    public class ExceptionHandlerMiddlewareTest
+    {
+        [Fact]
+        public async Task Invoke_ExceptionThrownResultsInClearedRouteValuesAndEndpoint()
+        {
+            // Arrange
+            var httpContext = CreateHttpContext();
+            httpContext.SetEndpoint(new Endpoint((_) => Task.CompletedTask, new EndpointMetadataCollection(), "Test"));
+            httpContext.Request.RouteValues["John"] = "Doe";
+
+            var optionsAccessor = CreateOptionsAccessor(
+                exceptionHandler: context =>
+                {
+                    Assert.Empty(context.Request.RouteValues);
+                    Assert.Null(context.GetEndpoint());
+                    return Task.CompletedTask;
+                });
+            var middleware = CreateMiddleware(_ => throw new InvalidOperationException(), optionsAccessor);
+
+            // Act & Assert
+            await middleware.Invoke(httpContext);
+        }
+
+        private HttpContext CreateHttpContext()
+        {
+            var httpContext = new DefaultHttpContext
+            {
+                RequestServices = new TestServiceProvider()
+            };
+
+            return httpContext;
+        }
+
+        private IOptions<ExceptionHandlerOptions> CreateOptionsAccessor(
+            RequestDelegate exceptionHandler = null,
+            string exceptionHandlingPath = null)
+        {
+            exceptionHandler ??= c => Task.CompletedTask;
+            var options = new ExceptionHandlerOptions()
+            {
+                ExceptionHandler = exceptionHandler,
+                ExceptionHandlingPath = exceptionHandlingPath,
+            };
+            var optionsAccessor = Mock.Of<IOptions<ExceptionHandlerOptions>>(o => o.Value == options);
+            return optionsAccessor;
+        }
+
+        private ExceptionHandlerMiddleware CreateMiddleware(
+            RequestDelegate next,
+            IOptions<ExceptionHandlerOptions> options)
+        {
+            next ??= c => Task.CompletedTask;
+            var listener = new DiagnosticListener("Microsoft.AspNetCore");
+
+            var middleware = new ExceptionHandlerMiddleware(
+                next,
+                NullLoggerFactory.Instance,
+                options,
+                listener);
+
+            return middleware;
+        }
+
+        private class TestServiceProvider : IServiceProvider
+        {
+            public object GetService(Type serviceType)
+            {
+                throw new NotImplementedException();
+            }
+        }
+    }
+}