I'm somehow running in circles... I try to create an interface proxy with target using Castle Dynamic Proxy. The proxy should
InterceptedException
if the invocation throws an InvalidOperationException
.e
if the invocation throws another exception e
.In other words, the interceptor should catch and convert a specific exception type, and not intercept in all other cases.
I got this working for synchronous methods. However, I need the same behavior for async Methods that return a task.
I tried adding a continuation to the returned task and inspect IsFaulted
and Exception
(similar to this answer. This works for methods that return Task
, but not for methods that return Task<T>
since my continuation is of type Task
(and I don't know what T
is in the interceptor).
public class ConvertNotFoundInterceptorTest
{
[Fact]
public void Non_throwing_func_returns_a_result()
{
Assert.Equal(43, RunTest(i => i + 1));
}
[Fact]
public void InvalidOperationExceptions_are_converted_to_IndexOutOfRangeExceptions()
{
var exception = Assert.Throws<AggregateException>(() => RunTest(i => { throw new InvalidOperationException("ugh"); }));
Assert.True(exception.InnerException is IndexOutOfRangeException);
}
[Fact]
public void Other_exceptions_are_preserved()
{
var exception = Assert.Throws<AggregateException>(() => RunTest(i => { throw new ArgumentException("ugh"); }));
Assert.True(exception.InnerException is ArgumentException);
}
private static int RunTest(Func<int, int> func)
{
var generator = new ProxyGenerator();
var proxiedSubject = generator.CreateInterfaceProxyWithTarget<ISubject>(new Subject(func), new ConvertNotFoundInterceptor());
return proxiedSubject.DoAsync(42).Result;
}
public interface ISubject
{
Task<int> DoAsync(int input);
}
public class Subject : ISubject
{
private readonly Func<int, int> _func;
public Subject(Func<int, int> func)
{
_func = func;
}
public async Task<int> DoAsync(int input)
{
return await Task.Run(() => _func(input));
}
}
}
public class ConvertNotFoundInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
var task = invocation.ReturnValue as Task;
if (task != null)
{
var continuation = task.ContinueWith(
t =>
{
if (t.Exception != null && t.Exception.InnerException is InvalidOperationException)
{
throw new IndexOutOfRangeException();
}
}, TaskContinuationOptions.OnlyOnFaulted);
// The following line fails (InvalidCastException: Unable to cast object
// of type 'System.Threading.Tasks.ContinuationTaskFromTask'
// to type 'System.Threading.Tasks.Task`1[System.Int32]'.)
invocation.ReturnValue = continuation;
}
}
}
Note that the implementation as shown here does not consider synchronous cases. I left that part out intentionally.
What is the correct way to add above interception logic to asynchronous methods?
答案 0 :(得分:4)
好的,这不适用于Task<dynamic>
,因为Castle Dynamic Proxy要求ReturnValue
是完全匹配的类型。但是,通过使用dynamic
进行调度,您可以相当优雅地完成此任务:
public class ConvertNotFoundInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
var task = invocation.ReturnValue as Task;
if (task != null)
invocation.ReturnValue = ConvertNotFoundAsync((dynamic)task);
}
private static async Task ConvertNotFoundAsync(Task source)
{
try
{
await source.ConfigureAwait(false);
}
catch (InvalidOperationException)
{
throw new IndexOutOfRangeException();
}
}
private static async Task<T> ConvertNotFoundAsync<T>(Task<T> source)
{
try
{
return await source.ConfigureAwait(false);
}
catch (InvalidOperationException)
{
throw new IndexOutOfRangeException();
}
}
}
我非常喜欢async
/ await
语法,因为它们使用ContinueWith
正确处理棘手的边缘情况。