1

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 an InvalidOperationException.
  • Throw 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.

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?

Community
  • 1
  • 1
theDmi
  • 17,546
  • 6
  • 71
  • 138
  • I believe you should be able to get this working by returning either a `Task` or `Task`, depending on what the intercepted method is returning. And a `try`/`catch` block around an `await` should work fine. Can you post your existing code? – Stephen Cleary Jun 15 '15 at 14:22
  • @StephenCleary I updated my answer with the existing code. Returning a Task (as in the example) or a Task does both not work, both produce the InvalidCastException. – theDmi Jun 17 '15 at 09:13

1 Answers1

5

OK, this doesn't work with Task<dynamic> because Castle Dynamic Proxy requires the ReturnValue to be the exact matching type. However, you can accomplish this fairly elegantly by using dynamic for dispatch:

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();
        }
    }
}

I strongly prefer the async/await syntax because they properly handle edge cases that are tricky using ContinueWith.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Nice, that works! Could you tell me what `source.ConfigureAwait(false)` does in this context? The documentation is not really helpful for me, I'm afraid. – theDmi Jun 17 '15 at 15:31
  • [By default, `await` captures a context.](http://blog.stephencleary.com/2012/02/async-and-await.html) (I describe this in detail on my blog). The `ConfigureAwait(false)` avoids that context capture, since it is not necessary here. – Stephen Cleary Jun 17 '15 at 16:56