28

My questions is related to this post Intercept the call to an async method using DynamicProxy

I want to implement interceptor that works with async methods that returns Task or Task<T> result.

I use next code for return ContinueWith result (in order that caller method wait while interceptor finishes work)

var task = invocation.ReturnValue as Task;
invocation.ReturnValue = task.ContinueWith(c => 
      { code that should execute after method finish });

Above code works fine for Task result, but in case of Task<T> result ContinueWith will change return type from Task<T> to Task. I need to call overloaded method ContinueWith that returns Task<T>, but for this I need to cast invocation.ReturnValue to Task<T>

I didn't find way to cast it dynamically in any way. Does anyone know how to make it?

I also tried to call this method via reflection, but parameter is labmda function that can't be passed directly.

Community
  • 1
  • 1
Anatoliy
  • 672
  • 5
  • 12
  • 1
    Check out https://msdn.microsoft.com/en-us/magazine/dn574805.aspx - article is for Unity interceptors, but "wrap the task" code does not depend on your DI container. – Alexei Levenkov Jan 22 '15 at 22:20

5 Answers5

24

After extensive research, I was able to create a solution that works for intercepting Synchronous Methods as well as Async Task and Async Task< TResult >.

Here is my code for an Exception Handling interceptor that works on all those method types, using Castle Dynamic Proxy. This pattern is adaptable for doing any sort of intercept you wish. The syntax will be a little cleaner for standard BeforeInvoke/AfterInvoke actions, but the concept should be the same.

(Other note: the IExceptionHandler interface in the example is a custom type, and not a common object.)

    private class AsyncExceptionHandlingInterceptor : IInterceptor
    {
        private static readonly MethodInfo handleAsyncMethodInfo = typeof(AsyncExceptionHandlingInterceptor).GetMethod("HandleAsyncWithResult", BindingFlags.Instance | BindingFlags.NonPublic);
        private readonly IExceptionHandler _handler;

        public AsyncExceptionHandlingInterceptor(IExceptionHandler handler)
        {
            _handler = handler;
        }

        public void Intercept(IInvocation invocation)
        {
            var delegateType = GetDelegateType(invocation);
            if (delegateType == MethodType.Synchronous)
            {
                _handler.HandleExceptions(() => invocation.Proceed());
            }
            if (delegateType == MethodType.AsyncAction)
            {
                invocation.Proceed();
                invocation.ReturnValue = HandleAsync((Task)invocation.ReturnValue);
            }
            if (delegateType == MethodType.AsyncFunction)
            {
                invocation.Proceed();
                ExecuteHandleAsyncWithResultUsingReflection(invocation);
            }
        }

        private void ExecuteHandleAsyncWithResultUsingReflection(IInvocation invocation)
        {
            var resultType = invocation.Method.ReturnType.GetGenericArguments()[0];
            var mi = handleAsyncMethodInfo.MakeGenericMethod(resultType);
            invocation.ReturnValue = mi.Invoke(this, new[] { invocation.ReturnValue });
        }

        private async Task HandleAsync(Task task)
        {
            await _handler.HandleExceptions(async () => await task);
        }

        private async Task<T> HandleAsyncWithResult<T>(Task<T> task)
        {
            return await _handler.HandleExceptions(async () => await task);
        }

        private MethodType GetDelegateType(IInvocation invocation)
        {
            var returnType = invocation.Method.ReturnType;
            if (returnType == typeof(Task))
                return MethodType.AsyncAction;
            if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
                return MethodType.AsyncFunction;
            return MethodType.Synchronous;
        }

        private enum MethodType
        {
            Synchronous,
            AsyncAction,
            AsyncFunction
        }
    }
Silas Reinagel
  • 4,155
  • 1
  • 21
  • 28
  • 1
    Thanks for providing this, it was exactly what I was looking for. Just so it's clear for others, here's the IExceptionHandler interface methods. In my implementation I actually renamed these classes to be generic for use in any interceptor that may intercept async methods. `void Handle(Action synchronousInvoker)`, `Task Handle(Func awaitableInvoker)`, `Task Handle(Func> awaitableInvoker)`. – Matt Scully Mar 03 '15 at 20:11
  • I see that 'HandleAsyncWithResult' method is never called. Did you forgot something in sample code? I see that in case of 'AsyncFunction' exceptions won't be handled. – Krzysztof Branicki Feb 11 '16 at 10:48
  • 1
    The 'HandleAsyncWithResult' method is called in a roundabout fashion from the 'ExecuteHandleAsyncWithResultUsingReflection' method. The exceptions are handled by the .NET Task objects, and correctly thrown when invoked. – Silas Reinagel Feb 11 '16 at 19:47
16

A better solution would be to use the dynamic keyword to bypass the compiler type checking and resolve the operation at run time:

public void Intercept(IInvocation invocation)
{
    invocation.Proceed();
    var method = invocation.MethodInvocationTarget;
    var isAsync = method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null;
    if (isAsync && typeof(Task).IsAssignableFrom(method.ReturnType))
    {
        invocation.ReturnValue = InterceptAsync((dynamic)invocation.ReturnValue);
    }
}

private static async Task InterceptAsync(Task task)
{
    await task.ConfigureAwait(false);
    // do the continuation work for Task...
}

private static async Task<T> InterceptAsync<T>(Task<T> task)
{
    T result = await task.ConfigureAwait(false);
    // do the continuation work for Task<T>...
    return result;
}
thepirat000
  • 12,362
  • 4
  • 46
  • 72
7

Having a need to intercept methods returning Task<TResult>, I've created an extension to Castle.Core that simplifies the process.

Castle.Core.AsyncInterceptor

The package is available to download on NuGet.

The solution is largely based on the answer from @silas-reinagel, but simplifies it by providing a new interface to implement IAsyncInterceptor. There are also further abstractions to that make interception similar to implementing Interceptor.

See the readme of the project for further details.

Community
  • 1
  • 1
James Skimming
  • 4,991
  • 4
  • 26
  • 32
0

The solutions from @Silas Reinagel and @thepirat000 didn't work for me, and I was not successful using Castle.Core.AsyncInterceptor solution from @James Skimming.

In my case, I'm intercepting an async method returning Task, and it should execute "after invocation.Proceed() code" only if there was no exception during the invocation.Proceed(). At the end I used @James Skimming's code sample, so this solution works only for intercepting async methods returning Task and not Task<TResult>:

public void Intercept(IInvocation invocation)
{
    _stepPriorInvocation();

    invocation.Proceed();
    Func<Task> continuation = async () =>
    {
        await (Task)invocation.ReturnValue;

        _stepAfterSuccessfulInvocation();
    };

    invocation.ReturnValue = continuation();

    void _stepPriorInvocation()
    {
    }

    void _stepAfterSuccessfulInvocation()
    {
    }
}

xhafan
  • 2,140
  • 1
  • 26
  • 26
-1

I've done this way:

invocation.Proceed(); 
object response;
Type type = invocation.ReturnValue?.GetType();
if (type != null && typeof(Task).IsAssignableFrom(type))
{
    var resultProperty = type.GetProperty("Result");
    response = resultProperty.GetValue(invocation.ReturnValue);
}