Preserve exception stack trace in ActionFilterAttribute (fixes #1316).
This commit is contained in:
Родитель
7cdd908408
Коммит
aed4ac8667
|
@ -2,6 +2,7 @@
|
|||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http.Controllers;
|
||||
|
@ -83,38 +84,63 @@ namespace System.Web.Http.Filters
|
|||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
HttpResponseMessage response = null;
|
||||
Exception exception = null;
|
||||
ExceptionDispatchInfo exceptionInfo = null;
|
||||
try
|
||||
{
|
||||
response = await continuation();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exception = e;
|
||||
exceptionInfo = ExceptionDispatchInfo.Capture(e);
|
||||
}
|
||||
|
||||
Exception exception;
|
||||
|
||||
if (exceptionInfo == null)
|
||||
{
|
||||
exception = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
exception = exceptionInfo.SourceException;
|
||||
}
|
||||
|
||||
HttpActionExecutedContext executedContext = new HttpActionExecutedContext(actionContext, exception)
|
||||
{
|
||||
Response = response
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
HttpActionExecutedContext executedContext = new HttpActionExecutedContext(actionContext, exception) { Response = response };
|
||||
await OnActionExecutedAsync(executedContext, cancellationToken);
|
||||
|
||||
if (executedContext.Response != null)
|
||||
{
|
||||
return executedContext.Response;
|
||||
}
|
||||
if (executedContext.Exception != null)
|
||||
{
|
||||
throw executedContext.Exception;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Catch is running because OnActionExecuted threw an exception, so we just want to re-throw the exception.
|
||||
// Catch is running because OnActionExecuted threw an exception, so we just want to re-throw.
|
||||
// We also need to reset the response to forget about it since a filter threw an exception.
|
||||
actionContext.Response = null;
|
||||
throw;
|
||||
}
|
||||
|
||||
if (executedContext.Response != null)
|
||||
{
|
||||
return executedContext.Response;
|
||||
}
|
||||
|
||||
Exception newException = executedContext.Exception;
|
||||
|
||||
if (newException != null)
|
||||
{
|
||||
if (newException == exception)
|
||||
{
|
||||
exceptionInfo.Throw();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw newException;
|
||||
}
|
||||
}
|
||||
|
||||
throw Error.InvalidOperation(SRResources.ActionFilterAttribute_MustSupplyResponseOrException, GetType().Name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -472,6 +472,98 @@ namespace System.Web.Http.Filters
|
|||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecuteActionFilterAsync_IfOnActionExecutedReplacesException_ThrowsNewException()
|
||||
{
|
||||
// Arrange
|
||||
Exception expectedReplacementException = CreateException();
|
||||
|
||||
using (HttpRequestMessage request = new HttpRequestMessage())
|
||||
{
|
||||
Mock<ActionFilterAttribute> mock = new Mock<ActionFilterAttribute>();
|
||||
mock.CallBase = true;
|
||||
mock
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<HttpActionExecutedContext>()))
|
||||
.Callback<HttpActionExecutedContext>((c) => c.Exception = expectedReplacementException);
|
||||
IActionFilter product = mock.Object;
|
||||
|
||||
HttpActionContext context = ContextUtil.CreateActionContext();
|
||||
Func<Task<HttpResponseMessage>> continuation = () =>
|
||||
CreateFaultedTask<HttpResponseMessage>(CreateException());
|
||||
|
||||
// Act
|
||||
Task<HttpResponseMessage> task = product.ExecuteActionFilterAsync(context, CancellationToken.None,
|
||||
continuation);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(task);
|
||||
task.WaitUntilCompleted();
|
||||
Assert.Equal(TaskStatus.Faulted, task.Status);
|
||||
Assert.NotNull(task.Exception);
|
||||
Exception exception = task.Exception.GetBaseException();
|
||||
Assert.Same(expectedReplacementException, exception);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecuteActionFilterAsync_IfFaultedTaskExceptionIsUnhandled_PreservesExceptionStackTrace()
|
||||
{
|
||||
// Arrange
|
||||
Exception originalException = CreateExceptionWithStackTrace();
|
||||
string expectedStackTrace = originalException.StackTrace;
|
||||
|
||||
using (HttpRequestMessage request = new HttpRequestMessage())
|
||||
{
|
||||
IActionFilter product = new TestableActionFilter();
|
||||
HttpActionContext context = ContextUtil.CreateActionContext();
|
||||
Func<Task<HttpResponseMessage>> continuation = () => CreateFaultedTask<HttpResponseMessage>(
|
||||
originalException);
|
||||
|
||||
// Act
|
||||
Task<HttpResponseMessage> task = product.ExecuteActionFilterAsync(context, CancellationToken.None,
|
||||
continuation);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(task);
|
||||
task.WaitUntilCompleted();
|
||||
Assert.Equal(TaskStatus.Faulted, task.Status);
|
||||
Assert.NotNull(task.Exception);
|
||||
Exception exception = task.Exception.GetBaseException();
|
||||
Assert.NotNull(expectedStackTrace);
|
||||
Assert.NotNull(exception);
|
||||
Assert.NotNull(exception.StackTrace);
|
||||
Assert.True(exception.StackTrace.StartsWith(expectedStackTrace));
|
||||
}
|
||||
}
|
||||
|
||||
private static Exception CreateException()
|
||||
{
|
||||
return new InvalidOperationException();
|
||||
}
|
||||
|
||||
private static Exception CreateExceptionWithStackTrace()
|
||||
{
|
||||
Exception exception;
|
||||
|
||||
try
|
||||
{
|
||||
throw CreateException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
return exception;
|
||||
}
|
||||
|
||||
private static Task<T> CreateFaultedTask<T>(Exception exception)
|
||||
{
|
||||
TaskCompletionSource<T> source = new TaskCompletionSource<T>();
|
||||
source.SetException(exception);
|
||||
return source.Task;
|
||||
}
|
||||
|
||||
public class TestableActionFilter : ActionFilterAttribute
|
||||
{
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче