Problem
I'm somehow running in circles... I try to create an interface proxy with target using Castle Dynamic Proxy. The proxy should
- Return the return value of the invocation if no exception is thrown (i.e. do nothing).
- Throw new
InterceptedException
if the invocation throws anInvalidOperationException
. - Throw
e
if the invocation throws another exceptione
.
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.
What I tried
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).
Tests that covers the three cases described above for async methods (XUnit.net)
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));
}
}
}
Interceptor
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.
Question
What is the correct way to add above interception logic to asynchronous methods?
OK, this doesn't work with
Task<dynamic>
because Castle Dynamic Proxy requires theReturnValue
to be the exact matching type. However, you can accomplish this fairly elegantly by usingdynamic
for dispatch:I strongly prefer the
async
/await
syntax because they properly handle edge cases that are tricky usingContinueWith
.