unitycontainer/interception

Async method support in IInterceptionBehaviour

Opened this issue · 10 comments

My usecase is that I'm trying to implement entry/exit/exception logging on an async method.
I an IInterceptionBehavoiur implementation, the call to getNext()(...) is performed asynchronously and I don't get to log the exit of the funciton.

ENikS commented

Unity as of now does not do anything asynchronously. Can you provide more info?

In case of an awaitable target of the interception, I should be able to await the result in the IInterceptionBehavior.Invoke. Should I provide a more elaborate example?

ENikS commented

Please do

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Unity;
using Unity.Interception.ContainerIntegration;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.Interceptors.InstanceInterceptors.InterfaceInterception;
using Unity.Interception.PolicyInjection.Pipeline;

namespace Program
{
    public class Program
    {
        public static void Main(string[] args)
        {
            IUnityContainer container = new UnityContainer().AddNewExtension<Interception>();
            container.RegisterType<IInterface, ConcreteClass>(
                new Interceptor<InterfaceInterceptor>(),
                new InterceptionBehavior<InterceptBehavior>());
            var myInterface = container.Resolve<IInterface>();
            myInterface.Method().Wait();
        }
    }

    public interface IInterface
    {
        Task Method();
    }

    public class ConcreteClass : IInterface
    {
        public async Task Method()
        {
            await Task.Delay(1000);
            Console.WriteLine("Method Called");
            await Task.Delay(1000);
        }
    }

    public class InterceptBehavior : IInterceptionBehavior
    {
        public bool WillExecute => true;

        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            Console.WriteLine("Before the call");
            var result = getNext()(input, getNext);

            Console.WriteLine("After the call");

            return result;
        }
    }
}

The output of this will be

Before the call
After the call
Method called

Because ConcreteClass's Method is async, and currently there is no way to await the result in the InterceptBehavior's Invoke method.

Even though I can work around that, with:

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            Console.WriteLine("Before the call");
            var result = getNext()(input, getNext);

            if (result.ReturnValue is Task res)
            {
                result.ReturnValue = res.ContinueWith(_ => Console.WriteLine("After the call"));
            }
            else
            {
                Console.WriteLine("After the call");
            }
            return result;
        }

it still feels like the async targets could be supported by the library.

ENikS commented

You are not alone in this thought.
The support for async is in preliminary stages of development

More information on how it can be handled at the moment:
https://msdn.microsoft.com/en-us/magazine/dn574805.aspx

Code download:
https://msdn.microsoft.com/magazine/msdnmag0214

  • One option to implement default support for async interception is a separate interceptor for sync and async method calls. The existing code can remain, and a new 'IAsyncInterceptionBehaviour' oid can be added. Core unity logic should choose the correct interceptor based on the method's type.
    Minor disadvantage: registering an interface with both async and sync methods would then have two interceptionBehaviours.
    An example of this can be found in the structureMap library:
    http://structuremap.github.io/dynamic-interception/

  • Other option is expanding the current InterceptionBehaviour with an extra InvokeAsync() method. Minor disadvantage: that will force the users to change their existing implementations (adding impl. for the async method).
    Advantage: registering an interface with both async and sync method will not change.

Edit:
Note on the code download: remove the 'configureAwait(false)' from the code, because that will lose the httpContext. If some logging is done afterwards which uses the DI container for eg. logging the username of the current user, and the DI container uses the HttpContext, an error occurs.

Hi, what's the progress of this feature?
Thx!

ENikS commented

Still in development

Can you provide a new status? Still in dev? Any ideas on when to expect this to be ready?

ENikS commented

The optimistic prognosis would be sometime in September