Anatoliy Anatoliy - 2 months ago 18
C# Question

Intercept async method that returns generic Task<> via DynamicProxy

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.

Answer

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