Preserve exception stack trace in ActionFilterAttribute (fixes #1316).

This commit is contained in:
davidmatson 2013-11-04 11:40:48 -08:00 коммит произвёл dougbu
Родитель 7cdd908408
Коммит aed4ac8667
2 изменённых файлов: 131 добавлений и 13 удалений

Просмотреть файл

@ -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
{
}