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

This is part five of my tuto­r­ial on Cas­tle Dynamic Proxy.

Our Freez­able library is start­ing to work quite well. How­ever, there are still few glitches we need to pol­ish. First of all, take a look at fol­low­ing screen­shot from our sam­ple application.

dptutorial_5_app_before

We have the sup­port for inter­cep­tion and log­ging in place, but cur­rently it's an all or noth­ing sce­nario. We started with inter­cept­ing every sin­gle method. Then we decided to lower the over­head, and inter­cept only prop­erty set­ters. Nei­ther of these solu­tions is per­fect.
The first one allowed us to be more flex­i­ble. We could log all meth­ods, ensure state immutabil­ity of the oth­ers, and if we had more inter­cep­tors, spe­cific to some sub­set of meth­ods, we could use them too. The down­side of that approach was, that each inter­cep­tor was called for each method.
To rem­edy that, we used Prox­y­Gen­er­a­tionHook to choose only the meth­ods we wanted to inter­cept. That cut it, but only par­tially. We still have every inter­cep­tor being called for each and every of those meth­ods we decided to inter­cept.
What we'd really want, is a more fine grained con­trol, that allows us to not only say which meth­ods we want to inter­cept, but also with which inter­cep­tors.
First to do that, we need to intro­duce some changes to out project. For now, Log­ging inter­cep­tor was the one that kept count of all its invo­ca­tion. Since we no longer want to have each inter­cep­tor called each time a method is inter­cepted, we will fac­tor out the Count prop­erty to a new inter­face IHas­Count, and make all inter­cep­tors imple­ment it.
Also we will change the Get­Inter­cept­ed­Meth­od­sCount­For to return a count for spe­cific interceptor.

With that we can now spec­ify our new require­ments 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?
Prox­y­Gen­er­a­tionOp­tions class, has a prop­erty Selec­tor of type IIn­ter­cep­torS­e­lec­tor, that we'll use to achieve that. The inter­face con­tains just one method:
dptutorial_5_iinterceptorSelector
The method gets called for each method being inter­cepted. It receives infor­ma­tion about prox­ied type, the method, and an array of all inter­cep­tors reg­is­tered with the proxy. It is expected to act upon this infor­ma­tion and return these inter­cep­tors it wishes to be used for the method. Also, while ProxyGenerationHook's Should­In­ter­cept­Method method gets called once, when gen­er­at­ing proxy type, InterceptorSelector's Select­In­ter­cep­tors 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 prop­erty set­ter 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 actu­ally 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 how­ever, they will all pass, except for the new one. That is because we still select for inter­cep­tion only prop­erty set­ters. We need to update our Freez­ableProx­y­Gen­er­a­tionHook. To demon­strate how selec­tor and proxy gen­er­a­tion hook work together we'll instruct the hook to not inter­cept meth­ods, only prop­er­ties. 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 actu­ally 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 imme­di­ately obvi­ous thing about that test. We check if we can inter­cept ToString method. In this par­tic­u­lar case we get to decide, if we want that method inter­cepted or not. How­ever if you set a break­point in the Should­In­ter­cept method, you'll notice, that we don't get asked that ques­tion for any other vir­tual method inher­ited from System.Object (GetH­ash­Code, Equals).
That is because Dynam­icProxy not allows you to inter­cept System.Object's (or System.MarshalByRefObject’s) meth­ods, unless they're over­rid­den in the proxy type (or in any of its base types). Since Pet class does over­ride ToString, we get the oppor­tu­nity to inter­cept this method, whereas all other meth­ods are not over­rid­den so we can't proxy them.

Here, for com­par­i­sion is the screen­shot from our appli­ca­tion after intro­duc­ing those changes:

dptutorial_5_app_after

The code is here.

Tech­no­rati Tags: , ,
  • Scott_M

    Is it pos­si­ble (or wise) to spec­ify inter­cep­tors at the ser­vice method level for WCF ser­vices (either in code or con­fig­u­ra­tion)? Specif­i­cally, it might be very handy to spec­ify which inter­cep­tors to use for a given WCF method by using method level attrib­utes. Would such a solu­tion be slow because of attribute lookups?

    thanks!

  • http://kozmic.pl/ Krzysztof Kozmic

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

    Talk­ing about via­bil­ity of such solu­tion it all depends on the spe­cific con­text. I do use this (per method inter­cep­tion), though not for WCF calls, but it does not mat­ter 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 con­sider using stan­dard WCF exten­sion points. As awk­ward as they are, most of the time they are the pre­ferred solution.