Fork me on GitHub

Aspect Oriented Programming with StructureMap.DynamicInterception Edit on GitHub


The Interception and Decorators page describes how to use "static" interception of created instances, mainly allowing to apply the Decorator pattern. It is "static" in a sense that you need know what interfaces you want to implement and you need to create decorators implementing all appropriate interfaces. It is almost always fine, but there are cases when you want to implement the same decorating logic for many interfaces which easily breaks the DRY principle. The most common examples are cross-cutting concerns such as logging, caching etc.

Dynamic proxy interceptor

Let's see it in action. Let's say we have


public interface IMathService
{
    int GetSquareRoot(int value);

    Task<int> GetSquareRootAsync(int value);
}
with implementation

private class MathService : IMathService
{
    public int GetSquareRoot(int value)
    {
        return (int)Math.Sqrt(value);
    }

    public async Task<int> GetSquareRootAsync(int value)
    {
        await Task.Yield();
        return (int)Math.Sqrt(value);
    }
}

If you need to apply dynamic interception to IMathService, first you implement either ISyncInterceptionBehavior or IAsyncInterceptionBehavior depending if your interception code needs to use async/await or not. For demonstration purposes, let's have


private class NegatingInterceptor : ISyncInterceptionBehavior
{
    public IMethodInvocationResult Intercept(ISyncMethodInvocation methodInvocation)
    {
        var argument = methodInvocation.GetArgument("value");
        var argumentValue = (int)argument.Value;
        if (argumentValue < 0)
        {
            argument.Value = -argumentValue;
        }
        return methodInvocation.InvokeNext();
    }
}
and

private class AsyncCachingInterceptor : IAsyncInterceptionBehavior
{
    private static readonly IDictionary<int, int> PrecalculatedValues = new Dictionary<int, int>
    {
        { 16, 4444 },
        { 10, 5555 },
    };

    public async Task<IMethodInvocationResult> InterceptAsync(IAsyncMethodInvocation methodInvocation)
    {
        var argument = methodInvocation.GetArgument("value");
        var argumentValue = (int)argument.Value;

        int result;
        return PrecalculatedValues.TryGetValue(argumentValue, out result)
            ? methodInvocation.CreateResult(result)
            : await methodInvocation.InvokeNextAsync().ConfigureAwait(false);
    }
}

Finally, we register interceptors with help of DynamicProxyInterceptor as follows:


[Theory]
[InlineData(111, 10)]
[InlineData(16, 4444)]
[InlineData(-16, 4444)]
public void CallSyncMethodWithSyncThenAsyncInterceptors(int value, int expectedResult)
{
    var container = new Container(x =>
    {
        x.For<IMathService>().Use<MathService>()
            .InterceptWith(new DynamicProxyInterceptor<IMathService>(new IInterceptionBehavior[]
            {
                new NegatingInterceptor(),
                new AsyncCachingInterceptor()
            }));
    });

    var service = container.GetInstance<IMathService>();

    service.GetSquareRoot(value).ShouldBe(expectedResult);
}

The idea is simple - for each method call of the intercepted instance, Intercept/InterceptAsync is called passing an IMethodInvocation instance allowing to get information about the intercepted method and modify arguments if needed before passing to the next interceptor. You have a choice to call the next interceptor in the chain via methodInvocation.InvokeNext/methodInvocation.InvokeNextAsync. Alternatively, you can return the result directly from the interceptor via methodInvocation.CreateResult.

Finally, you can also throw exceptions from interceptors either directly or by returning methodInvocation.CreateExceptionResult.

Dynamic proxy policy

As described on the Interception and Decorators page, you can create an interception policy if you need to apply interceptors to many types by certain filter. DynamicProxyInterceptorPolicy makes it easier when it comes to dynamic interceptors. See the example below:


[Fact]
public void UseInterceptionPolicy()
{
    var container = new Container(x =>
    {
        x.Policies.Interceptors(new DynamicProxyInterceptorPolicy(new NegatingInterceptor(), new CachingInterceptor()));

        x.For<IMathService>().Use<MathService>();
    });

    var service = container.GetInstance<IMathService>();

    service.GetSquareRoot(-10).ShouldBe(5555);
}

Check DynamicProxyInterceptorPolicy constructors to find the most suitable overload for your purposes.