diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs index d0c0b4dcc0..7732a60160 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs @@ -68,6 +68,11 @@ public virtual ExceptionDispatchInfo ExceptionDispatchInfo } } + /// + /// Gets or sets an indication that the has been handled. + /// + public virtual bool ExceptionHandled { get; set; } = false; + /// /// Gets or sets the . /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs index 252b0a0696..084ec71ef6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs @@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Internal @@ -354,7 +353,7 @@ private async Task InvokeResourceFilterAsync() Result = _exceptionContext.Result, }; } - else if (_exceptionContext.Exception != null) + else if (_exceptionContext.Exception != null && !_exceptionContext.ExceptionHandled) { // If we get here, this means that we have an unhandled exception. // Exception filted didn't handle this, so send it on to resource filters. @@ -412,7 +411,7 @@ private async Task InvokeExceptionFilterAsync() await InvokeExceptionFilterAsync(); Debug.Assert(_exceptionContext != null); - if (_exceptionContext.Exception != null) + if (_exceptionContext.Exception != null && !_exceptionContext.ExceptionHandled) { _diagnosticSource.BeforeOnExceptionAsync(_exceptionContext, current.FilterAsync); @@ -422,7 +421,7 @@ private async Task InvokeExceptionFilterAsync() _diagnosticSource.AfterOnExceptionAsync(_exceptionContext, current.FilterAsync); - if (_exceptionContext.Exception == null) + if (_exceptionContext.Exception == null || _exceptionContext.ExceptionHandled) { _logger.ExceptionFilterShortCircuited(current.FilterAsync); } @@ -435,7 +434,7 @@ private async Task InvokeExceptionFilterAsync() await InvokeExceptionFilterAsync(); Debug.Assert(_exceptionContext != null); - if (_exceptionContext.Exception != null) + if (_exceptionContext.Exception != null && !_exceptionContext.ExceptionHandled) { _diagnosticSource.BeforeOnException(_exceptionContext, current.Filter); @@ -445,7 +444,7 @@ private async Task InvokeExceptionFilterAsync() _diagnosticSource.AfterOnException(_exceptionContext, current.Filter); - if (_exceptionContext.Exception == null) + if (_exceptionContext.Exception == null || _exceptionContext.ExceptionHandled) { _logger.ExceptionFilterShortCircuited(current.Filter); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs index 9dccafb7f4..c695754142 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs @@ -144,7 +144,7 @@ public async Task InvokeAction_InvokesAsyncExceptionFilter_WhenActionThrows() } [Fact] - public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit() + public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionNull() { // Arrange var filter1 = new Mock(MockBehavior.Strict); @@ -171,12 +171,38 @@ public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit() } [Fact] - public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit() + public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionHandled() { // Arrange var filter1 = new Mock(MockBehavior.Strict); + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnException(It.IsAny())) + .Callback(context => + { + context.ExceptionHandled = true; + }) + .Verifiable(); + + var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter2.Verify( + f => f.OnException(It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionNull() + { + // Arrange + var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); + filter2 .Setup(f => f.OnExceptionAsync(It.IsAny())) .Callback(context => @@ -187,6 +213,34 @@ public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit() .Returns((context) => Task.FromResult(true)) .Verifiable(); + var filterMetadata = new IFilterMetadata[] { filter1.Object, filter2.Object }; + var invoker = CreateInvoker(filterMetadata, actionThrows: true); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter2.Verify( + f => f.OnExceptionAsync(It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionHandled() + { + // Arrange + var filter1 = new Mock(MockBehavior.Strict); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnExceptionAsync(It.IsAny())) + .Callback(context => + { + context.ExceptionHandled = true; + }) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object }, actionThrows: true); // Act diff --git a/test/WebSites/FiltersWebSite/Filters/ShortCircuitExceptionFilter.cs b/test/WebSites/FiltersWebSite/Filters/ShortCircuitExceptionFilter.cs index b840ab3d41..4dad66a947 100644 --- a/test/WebSites/FiltersWebSite/Filters/ShortCircuitExceptionFilter.cs +++ b/test/WebSites/FiltersWebSite/Filters/ShortCircuitExceptionFilter.cs @@ -9,7 +9,7 @@ public class ShortCircuitExceptionFilter : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { - context.Exception = null; + context.ExceptionHandled = true; } } } \ No newline at end of file