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.