Search code examples
async-awaitaopsimple-injectorinterception

Simple Injector interception in async/await code


I am starting a new project and am thinking of using Simple Injector interception (https://simpleinjector.readthedocs.io/en/latest/InterceptionExtensions.html) for tracing method entry/exit and logging parameters and return values etc. I have used this interceptor in the past and it works great. But my previous projects were not async/await. This new project has a lot of methods that are all async/await and I was wondering

  • will this interceptor work for async/await methods?
  • what changes are required in this interceptor to make it work for async/await methods?

I understand that decorators are a much better pattern than interception but writing a decorator for each and every interface that I want to trace is not something that I am looking forward to doing.

UPDATE: I have tried this interceptor in my async/await code and it does inject my tracing code. However, I was getting weird results in some parts of my application. I didn't get chance to dig deeper into why disabling interception would make it work fine and why when interception was enabled it would not work as expected. It could very well be something wrong with my code.

I was hoping if someone, who has already used this interception extension in their code, would be able to point me in the right direction.


Solution

  • will this interceptor work for async/await methods?

    Async code in C# is syntactic sugar on top of Task. This means that if your code needs to do anything useful after a call to an async method, you will have call ContinueWith on the returned Task (or use the C# syntax). If you take asynchronous into consideration in your interceptor, you won't be able to execute logic after the wrapped object.

    So to make this work, you will have to explicitly check whether the wrapped method returns Task and if that's the case, you should make things async by hooking your 'after' code using ContinueWith.

    This is one of the many reasons I consider interception to be inferior to using decorators. Decorators allow your code to be much cleaner, refrain from using reflection, give complete compile time support, give better performance, prevent having to depend on an interception library, and force you into a much more SOLID application design.

    That said, the documentation's MonitoringInterceptor will look as follows when it takes asynchronicity into consideration:

    class MonitoringInterceptor : IInterceptor
    {
        private readonly ILogger logger;
    
        public MonitoringInterceptor(ILogger logger) {
            this.logger = logger;
        }
    
        public void Intercept(IInvocation invocation) {
            var watch = Stopwatch.StartNew();
    
            // Calls the decorated instance.
            invocation.Proceed();
    
            var task = invocation.ReturnValue as Task;
    
            if (task != null) {
                invocation.ReturnValue = LogElapsedAsync(task, invocation, watch);
            } else {
                LogElapsed(invocation, watch);
            }
        }
    
        private async Task LogElapsedAsync(Task task, IInvocation i, Stopwatch w) {
            await task;
            LogElapsed(i, w);
        }
    
        private void LogElapsed(IInvocation invocation, Stopwatch watch) {
            var decoratedType = invocation.InvocationTarget.GetType();
    
            this.logger.Log(string.Format("{0} executed in {1} ms.",
                decoratedType.Name, watch.ElapsedMilliseconds));
        }
    }