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