Castle Dynamic Proxy tutorial part V: InterceptorSelector, fine grained control over proxying

This is part five of my tutorial on Castle Dynamic Proxy.

Our Freezable library is starting to work quite well. However, there are still few glitches we need to polish. First of all, take a look at following screenshot from our sample application.

dptutorial_5_app_before

We have the support for interception and logging in place, but currently it’s an all or nothing scenario. We started with intercepting every single method. Then we decided to lower the overhead, and intercept only property setters. Neither of these solutions is perfect.
The first one allowed us to be more flexible. We could log all methods, ensure state immutability of the others, and if we had more interceptors, specific to some subset of methods, we could use them too. The downside of that approach was, that each interceptor was called for each method.
To remedy that, we used ProxyGenerationHook to choose only the methods we wanted to intercept. That cut it, but only partially. We still have every interceptor being called for each and every of those methods we decided to intercept.
What we’d really want, is a more fine grained control, that allows us to not only say which methods we want to intercept, but also with which interceptors.
First to do that, we need to introduce some changes to out project. For now, Logging interceptor was the one that kept count of all its invocation. Since we no longer want to have each interceptor called each time a method is intercepted, we will factor out the Count property to a new interface IHasCount, and make all interceptors implement it.
Also we will change the GetInterceptedMethodsCountFor to return a count for specific interceptor.

With that we can now specify our new requirements with a new test.

[Fact]
public void Freezable_should_log_getters_and_setters()
{
    var pet = Freezable.MakeFreezable<Pet>();
    pet.Age = 4;
    var age = pet.Age;
    int logsCount = GetInterceptedMethodsCountFor<CallLoggingInterceptor>( pet );
    int freezeCount = GetInterceptedMethodsCountFor<FreezableInterceptor>( pet );
    Assert.Equal( 2, logsCount );
    Assert.Equal( 1, freezeCount );
}

As expected, if we run the test now, it will fail. So how do we make it pass?
ProxyGenerationOptions class, has a property Selector of type IInterceptorSelector, that we’ll use to achieve that. The interface contains just one method:
dptutorial_5_iinterceptorSelector
The method gets called for each method being intercepted. It receives information about proxied type, the method, and an array of all interceptors registered with the proxy. It is expected to act upon this information and return these interceptors it wishes to be used for the method. Also, while ProxyGenerationHook’s ShouldInterceptMethod method gets called once, when generating proxy type, InterceptorSelector’s SelectInterceptors method is called for each instance of that type, just before the first call to that method.

So, what we need is to inspect the given method to see if it is a property setter and if it is not, not return the FreezableInterceptor.

public class FreezableInterceptorSelector : IInterceptorSelector
{
    public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
    {
        if (IsSetter(method))
            return interceptors;
        return interceptors.Where( i => !( i is FreezableInterceptor ) ).ToArray();
    }
 
    private bool IsSetter( MethodInfo method )
    {
        return method.IsSpecialName && method.Name.StartsWith( "set_", StringComparison.Ordinal );
    }
}

We also need to change Freezable.MakeFreezable method to actually make use of our new FreezableInterceptor.

private static readonly IInterceptorSelector _selector = new FreezableInterceptorSelector();
 
public static TFreezable MakeFreezable<TFreezable>() where TFreezable : class, new()
{
    var freezableInterceptor = new FreezableInterceptor();
    var options = new ProxyGenerationOptions(new FreezableProxyGenerationHook()) { Selector = _selector };
    var proxy = _generator.CreateClassProxy(typeof(TFreezable), options, new CallLoggingInterceptor(), freezableInterceptor);
    return proxy as TFreezable;
}

If we run the tests however, they will all pass, except for the new one. That is because we still select for interception only property setters. We need to update our FreezableProxyGenerationHook. To demonstrate how selector and proxy generation hook work together we’ll instruct the hook to not intercept methods, only properties. Here’s the updated code:

public bool ShouldInterceptMethod(Type type, MethodInfo memberInfo)
{
    return memberInfo.IsSpecialName &&
           ( IsSetterName( memberInfo.Name ) ||
             IsGetterName( memberInfo.Name ) );
}
 
private bool IsGetterName( string name )
{
    return name.StartsWith( "get_", StringComparison.Ordinal );
}
 
private bool IsSetterName( string name )
{
    return name.StartsWith("set_", StringComparison.Ordinal);
}

And the test to check if this actually works:

[Fact]
public void Freezable_should_not_intercept_methods()
{
 
    var pet = Freezable.MakeFreezable<Pet>();
    pet.ToString();
    int logsCount = GetInterceptedMethodsCountFor<CallLoggingInterceptor>(pet);
    int freezeCount = GetInterceptedMethodsCountFor<FreezableInterceptor>(pet);
 
    // base implementation of ToString calls each property getter, that we intercept
    // so there will be 3 calls if method is not intercepter, otherwise 4.
    Assert.Equal(3, logsCount);
    Assert.Equal(0, freezeCount);
}

There’s one not immediately obvious thing about that test. We check if we can intercept ToString method. In this particular case we get to decide, if we want that method intercepted or not. However if you set a breakpoint in the ShouldIntercept method, you’ll notice, that we don’t get asked that question for any other virtual method inherited from System.Object (GetHashCode, Equals).
That is because DynamicProxy not allows you to intercept System.Object’s (or System.MarshalByRefObject’s) methods, unless they’re overridden in the proxy type (or in any of its base types). Since Pet class does override ToString, we get the opportunity to intercept this method, whereas all other methods are not overridden so we can’t proxy them.

Here, for comparision is the screenshot from our application after introducing those changes:

dptutorial_5_app_after

The code is here.

Technorati Tags: , ,

Comments

Scott_M says:

Is it possible (or wise) to specify interceptors at the service method level for WCF services (either in code or configuration)? Specifically, it might be very handy to specify which interceptors to use for a given WCF method by using method level attributes. Would such a solution be slow because of attribute lookups?

thanks!

Attribute lookup is one of these things you have to do only once if you cache it, so perf hit is neglectable.

Talking about viability of such solution it all depends on the specific context. I do use this (per method interception), though not for WCF calls, but it does not matter that much, since at this level it does not do much difference.

So my answer would be – if you need it, sure – why not. Although you might also consider using standard WCF extension points. As awkward as they are, most of the time they are the preferred solution.