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 @@ namespace Microsoft.AspNetCore.Mvc.Filters
}
}
+ ///
+ /// 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.Controllers;
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 @@ namespace Microsoft.AspNetCore.Mvc.Internal
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 @@ namespace Microsoft.AspNetCore.Mvc.Internal
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 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_diagnosticSource.AfterOnExceptionAsync(_exceptionContext, current.FilterAsync);
- if (_exceptionContext.Exception == null)
+ if (_exceptionContext.Exception == null || _exceptionContext.ExceptionHandled)
{
_logger.ExceptionFilterShortCircuited(current.FilterAsync);
}
@@ -435,7 +434,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
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 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_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 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
- public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit()
+ public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionNull()
{
// Arrange
var filter1 = new Mock(MockBehavior.Strict);
@@ -171,7 +171,62 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[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 =>
+ {
+ filter2.ToString();
+ context.Exception = null;
+ })
+ .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);
@@ -181,8 +236,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Setup(f => f.OnExceptionAsync(It.IsAny()))
.Callback(context =>
{
- filter2.ToString();
- context.Exception = null;
+ context.ExceptionHandled = true;
})
.Returns((context) => Task.FromResult(true))
.Verifiable();
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 @@ namespace FiltersWebSite
{
public override void OnException(ExceptionContext context)
{
- context.Exception = null;
+ context.ExceptionHandled = true;
}
}
}
\ No newline at end of file