Interception ExtensionsΒΆ

Adding interception abilities to Simple Injector.

using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;

using SimpleInjector;

public interface IInterceptor
{
    void Intercept(IInvocation invocation);
}

public interface IInvocation
{
    object InvocationTarget { get; }
    object ReturnValue { get; set; }
    object[] Arguments { get; }
    void Proceed();
    MethodBase GetConcreteMethod();
}

// Extension methods for interceptor registration
// NOTE: These extension methods can only intercept interfaces, not abstract types.
public static class InterceptorExtensions
{
    public static void InterceptWith<TInterceptor>(this Container container,
        Func<Type, bool> predicate)
        where TInterceptor : class, IInterceptor {
        RequiresIsNotNull(container, "container");
        RequiresIsNotNull(predicate, "predicate");
        container.Options.ConstructorResolutionBehavior.GetConstructor(
            typeof(TInterceptor), typeof(TInterceptor));

        var interceptWith = new InterceptionHelper(container) {
            BuildInterceptorExpression =
                e => BuildInterceptorExpression<TInterceptor>(container),
            Predicate = type => predicate(type)
        };

        container.ExpressionBuilt += interceptWith.OnExpressionBuilt;
    }

    public static void InterceptWith(this Container container,
        Func<IInterceptor> interceptorCreator, Func<Type, bool> predicate) {
        RequiresIsNotNull(container, "container");
        RequiresIsNotNull(interceptorCreator, "interceptorCreator");
        RequiresIsNotNull(predicate, "predicate");

        var interceptWith = new InterceptionHelper(container) {
            BuildInterceptorExpression =
                e => Expression.Invoke(Expression.Constant(interceptorCreator)),
            Predicate = type => predicate(type)
        };

        container.ExpressionBuilt += interceptWith.OnExpressionBuilt;
    }

    public static void InterceptWith(this Container container,
        Func<ExpressionBuiltEventArgs, IInterceptor> interceptorCreator,
        Func<Type, bool> predicate) {
        RequiresIsNotNull(container, "container");
        RequiresIsNotNull(interceptorCreator, "interceptorCreator");
        RequiresIsNotNull(predicate, "predicate");

        var interceptWith = new InterceptionHelper(container) {
            BuildInterceptorExpression = e => Expression.Invoke(
                Expression.Constant(interceptorCreator),
                Expression.Constant(e)),
            Predicate = type => predicate(type)
        };

        container.ExpressionBuilt += interceptWith.OnExpressionBuilt;
    }

    public static void InterceptWith(this Container container,
        IInterceptor interceptor, Func<Type, bool> predicate) {
        RequiresIsNotNull(container, "container");
        RequiresIsNotNull(interceptor, "interceptor");
        RequiresIsNotNull(predicate, "predicate");

        var interceptWith = new InterceptionHelper(container) {
            BuildInterceptorExpression = e => Expression.Constant(interceptor),
            Predicate = predicate
        };

        container.ExpressionBuilt += interceptWith.OnExpressionBuilt;
    }

    [DebuggerStepThrough]
    private static Expression BuildInterceptorExpression<TInterceptor>(
        Container container)
        where TInterceptor : class
    {
        var interceptorRegistration = container.GetRegistration(typeof(TInterceptor));

        if (interceptorRegistration == null) {
            // This will throw an ActivationException
            container.GetInstance<TInterceptor>();
        }

        return interceptorRegistration.BuildExpression();
    }

    private static void RequiresIsNotNull(object instance, string paramName) {
        if (instance == null) {
            throw new ArgumentNullException(paramName);
        }
    }

    private class InterceptionHelper
    {
        private static readonly MethodInfo NonGenericInterceptorCreateProxyMethod = (
            from method in typeof(Interceptor).GetMethods()
            where method.Name == "CreateProxy"
            where method.GetParameters().Length == 3
            select method)
            .Single();

        public InterceptionHelper(Container container) {
            this.Container = container;
        }

        internal Container Container { get; private set; }

        internal Func<ExpressionBuiltEventArgs, Expression> BuildInterceptorExpression
        {
            get;
            set;
        }

        internal Func<Type, bool> Predicate { get; set; }

        [DebuggerStepThrough]
        public void OnExpressionBuilt(object sender, ExpressionBuiltEventArgs e) {
            if (this.Predicate(e.RegisteredServiceType)) {
                ThrowIfServiceTypeNotInterface(e);
                e.Expression = this.BuildProxyExpression(e);
            }
        }

        [DebuggerStepThrough]
        private static void ThrowIfServiceTypeNotInterface(ExpressionBuiltEventArgs e) {
            // NOTE: We can only handle interfaces, because
            // System.Runtime.Remoting.Proxies.RealProxy only supports interfaces.
            if (!e.RegisteredServiceType.IsInterface) {
                throw new NotSupportedException("Can't intercept type " +
                    e.RegisteredServiceType.Name + " because it is not an interface.");
            }
        }

        [DebuggerStepThrough]
        private Expression BuildProxyExpression(ExpressionBuiltEventArgs e) {
            var interceptor = this.BuildInterceptorExpression(e);

            // Create call to
            // (ServiceType)Interceptor.CreateProxy(Type, IInterceptor, object)
            var proxyExpression =
                Expression.Convert(
                    Expression.Call(NonGenericInterceptorCreateProxyMethod,
                        Expression.Constant(e.RegisteredServiceType, typeof(Type)),
                        interceptor,
                        e.Expression),
                    e.RegisteredServiceType);

            if (e.Expression is ConstantExpression && interceptor is ConstantExpression) {
                return Expression.Constant(CreateInstance(proxyExpression),
                    e.RegisteredServiceType);
            }

            return proxyExpression;
        }

        [DebuggerStepThrough]
        private static object CreateInstance(Expression expression) {
            var instanceCreator = Expression.Lambda<Func<object>>(expression,
                new ParameterExpression[0])
                .Compile();

            return instanceCreator();
        }
    }
}

public static class Interceptor
{
    public static T CreateProxy<T>(IInterceptor interceptor, T realInstance) {
        return (T)CreateProxy(typeof(T), interceptor, realInstance);
    }

    [DebuggerStepThrough]
    public static object CreateProxy(Type serviceType, IInterceptor interceptor,
        object realInstance) {
        var proxy = new InterceptorProxy(serviceType, realInstance, interceptor);
        return proxy.GetTransparentProxy();
    }

    private sealed class InterceptorProxy : RealProxy
    {
        private static MethodBase GetTypeMethod = typeof(object).GetMethod("GetType");

        private object realInstance;
        private IInterceptor interceptor;

        [DebuggerStepThrough]
        public InterceptorProxy(Type classToProxy, object realInstance,
            IInterceptor interceptor)
            : base(classToProxy) {
            this.realInstance = realInstance;
            this.interceptor = interceptor;
        }

        public override IMessage Invoke(IMessage msg) {
            if (msg is IMethodCallMessage) {
                var message = (IMethodCallMessage)msg;

                if (object.ReferenceEquals(message.MethodBase, GetTypeMethod)) {
                    return this.Bypass(message);
                } else {
                    return this.InvokeMethodCall(message);
                }
            }

            return msg;
        }

        private IMessage InvokeMethodCall(IMethodCallMessage message) {
            var invocation = new Invocation {
                Proxy = this,
                Message = message,
                Arguments = message.Args };

            invocation.Proceeding += () => {
                invocation.ReturnValue = message.MethodBase.Invoke(
                    this.realInstance, invocation.Arguments);
            };

            this.interceptor.Intercept(invocation);
            return new ReturnMessage(invocation.ReturnValue, invocation.Arguments,
                invocation.Arguments.Length, null, message);
        }

        private IMessage Bypass(IMethodCallMessage message) {
            object value = message.MethodBase.Invoke(this.realInstance, message.Args);

            return new ReturnMessage(value, message.Args, message.Args.Length, null, message);
        }

        private class Invocation : IInvocation
        {
            public event Action Proceeding;
            public InterceptorProxy Proxy { get; set; }
            public object[] Arguments { get; set; }
            public IMethodCallMessage Message { get; set; }
            public object ReturnValue { get; set; }

            public object InvocationTarget {
                get { return this.Proxy.realInstance; }
            }

            public void Proceed() {
                this.Proceeding();
            }

            public MethodBase GetConcreteMethod() {
                return this.Message.MethodBase;
            }
        }
    }
}

After copying the previous code snippet to your project, you can add interception using the following lines of code:

// Register a MonitoringInterceptor to intercept all interface
// service types, which type name end with the text 'Service'.
container.InterceptWith<MonitoringInterceptor>(
    serviceType => serviceType.Name.EndsWith("Service"));

// When the interceptor (and its dependencies) are thread-safe,
// it can be registered as singleton to prevent a new instance
// from being created and each call. When the intercepted service
// and both the interceptor are both singletons, the returned
// (proxy) instance will be a singleton as well.
container.RegisterSingle<MonitoringInterceptor>();

// Here is an example of an interceptor implementation.
// NOTE: Interceptors must implement the IInterceptor interface:
private 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 decoratedType = invocation.InvocationTarget.GetType();

        this.logger.Log(string.Format("{0} executed in {1} ms.",
            decoratedType.Name, watch.ElapsedMilliseconds));
    }
}