Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to create an async inteceptor using Castle.DynamicProxy?

We basically have a class that looks like this below that is using the Castle.DynamicProxy for Interception.

using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Castle.DynamicProxy;

namespace SaaS.Core.IoC
{
    public abstract class AsyncInterceptor : IInterceptor
    {
        private readonly ILog _logger;

        private readonly ConcurrentDictionary<Type, Func<Task, IInvocation, Task>> wrapperCreators =
            new ConcurrentDictionary<Type, Func<Task, IInvocation, Task>>();

        protected AsyncInterceptor(ILog logger)
        {
            _logger = logger;
        }

        void IInterceptor.Intercept(IInvocation invocation)
        {
            if (!typeof(Task).IsAssignableFrom(invocation.Method.ReturnType))
            {
                InterceptSync(invocation);
                return;
            }

            try
            {
                CheckCurrentSyncronizationContext();
                var method = invocation.Method;

                if ((method != null) && typeof(Task).IsAssignableFrom(method.ReturnType))
                {
                    var taskWrapper = GetWrapperCreator(method.ReturnType);
                    Task.Factory.StartNew(
                        async () => { await InterceptAsync(invocation, taskWrapper).ConfigureAwait(true); }
                        , // this will use current synchronization context
                        CancellationToken.None,
                        TaskCreationOptions.AttachedToParent,
                        TaskScheduler.FromCurrentSynchronizationContext()).Wait();
                }
            }
            catch (Exception ex)
            {
                //this is not really burring the exception
                //excepiton is going back in the invocation.ReturnValue which 
                //is a Task that failed. with the same excpetion 
                //as ex.
            }
        }
....

Initially this code was:

Task.Run(async () => { await InterceptAsync(invocation, taskWrapper)).Wait()

But we were losing HttpContext after any call to this, so we had to switch it to:

Task.Factory.StartNew 

So we could pass in the TaskScheduler.FromCurrentSynchronizationContext()

All of this is bad because we are really just swapping one thread for another thread. I would really love to change the signature of

void IInterceptor.Intercept(IInvocation invocation)

to

async Task IInterceptor.Intercept(IInvocation invocation)

And get rid of the Task.Run or Task.Factory and just make it:

await InterceptAsync(invocation, taskWrapper);

The problem is Castle.DynamicProxy IInterecptor won't allow this. I really want do an await in the Intercept. I could do .Result but then what is the point of the async call I am calling? Without being able to do the await I lose out of the benefit of it being able to yield this threads execution. I am not stuck with Castle Windsor for their DynamicProxy so I am looking for another way to do this. We have looked into Unity, but I don't want to replace our entire AutoFac implementation.

Any help would be appreciated.

like image 846
Eric Renken Avatar asked Sep 19 '16 19:09

Eric Renken


People also ask

How to implement asyncinterceptor for interception?

Create a class that extends the abstract base class AsyncInterceptorBase, then register it for interception in the same was as IInterceptor using the ProxyGenerator extension methods, e.g.

Why do we use proxy interceptors in execute method?

If you look at the Execute method, you will see it is a sequence of four methods, each of them ( [ LoadData, CheckIfApproved, AddDigitalSignature, and SubmitData) must be virtual and I will explain why. We will use proxy interceptors to intercept the execution of each of the used methods.

What is a dynamic proxy in Java?

What is a dynamic proxy? Let’s begin with a simple definition – a proxy acts as an interception mechanism to a class (or interface), in a transparent way and can allow the developer to intercept calls to the original class and add or change functionality on the original class.

How to use an interceptor with a target object?

You have to use the correct overload and pass in both the target object and the interceptor you wish to use. Method should look something like this: Thanks for contributing an answer to Stack Overflow!


1 Answers

All of this is bad because we are really just swapping one thread for another thread.

True. Also because the StartNew version isn't actually waiting for the method to complete; it will only wait until the first await. But if you add an Unwrap() to make it wait for the complete method, then I strongly suspect you'll end up with a deadlock.

The problem is Castle.DynamicProxy IInterecptor won't allow this.

IInterceptor does have a design limitation that it must proceed synchronously. So this limits your interception capabilities: you can inject synchronous code before or after the asynchronous method, and asynchronous code after the asynchronous method. There's no way to inject asynchronous code before the asynchronous method. It's just a limitation of DynamicProxy, one that would be extremely painful to correct (as in, break all existing user code).

To do the kinds of injection that is supported, you have to change your thinking a bit. One of the valid mental models of async is that a Task returned from a method represents the execution of that method. So, to append code to that method, you would call the method directly and then replace the task return value with an augmented one.

So, something like this (for return types of Task):

protected abstract void PreIntercept(); // must be sync
protected abstract Task PostInterceptAsync(); // may be sync or async

// This method will complete when PostInterceptAsync completes.
private async Task InterceptAsync(Task originalTask)
{
  // Asynchronously wait for the original task to complete
  await originalTask;

  // Asynchronous post-execution
  await PostInterceptAsync();
}

public void Intercept(IInvocation invocation)
{
  // Run the pre-interception code.
  PreIntercept();

  // *Start* the intercepted asynchronous method.
  invocation.Proceed();

  // Replace the return value so that it only completes when the post-interception code is complete.
  invocation.ReturnValue = InterceptAsync((Task)invocation.ReturnValue);
}

Note that the PreIntercept, the intercepted method, and PostInterceptAsync are all run in the original (ASP.NET) context.

P.S. A quick Google search for async DynamicProxy resulted in this. I don't have any idea how stable it is, though.

like image 121
Stephen Cleary Avatar answered Oct 27 '22 18:10

Stephen Cleary