Castle Dynamic Proxy tutorial part III: Selecting which methods to intercept

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

We’ll start by updat­ing our Cal­l­Log­ging­In­ter­cep­tor class, so that we can use it in tests. What we need from it, is to enhance its func­tion­al­ity, so that it not only logs (to the Con­sole) the calls, but also keeps a count of calls. To do that we add a prop­erty called Count, which gets incre­mented each time a method is called. It’s a triv­ial code, so I' won’t show it here.

With that, we can now write our first test. We want prop­erty set­ters to fire tests for object freez­abil­ity. Get­ters are safe in this regard and test always passes, so it’s an unnec­es­sary over­head. We want our get­ters to not get inter­cepted, so let’s write a test for that.

[Fact]
public void Freezable_should_not_intercept_property_getters()
{
    var pet = Freezable.MakeFreezable<Pet>();
    Freezable.Freeze(pet);
    var notUsed = pet.Age; //should not intercept
    var interceptedMethodsCount = GetInterceptedMethodsCountFor(pet);
    Assert.Equal(0, interceptedMethodsCount);
}

Let us not con­cern our­selves with Get­Inter­cept­ed­Meth­od­sCount­For method just yet. For now let’s only say that it just does what its name sug­gests. We’ll inspect its imple­men­ta­tion later. Now, we run the test, and – as expected, it fails.

dptutorial_3_getter_failed_test

So how do we make it pass? What we need is some kind of mech­a­nism that will enable us to choose, which meth­ods we want to inter­cept, and which we don’t. For­tu­nately Dynamic Proxy enables that. It exposes a hook that enables us to plug in into its proxy gen­er­a­tion pipeline, in form of… IProx­y­Gen­er­a­tionHook inter­face (you wouldn’t guess, would you?).

dptutorial_3_IProxyGenerationHook

Two really inter­est­ing meth­ods are Non­Vir­tualMem­ber­Noti­fi­ca­tion, that enables us to act, when user wants to proxy a type that has some non-virtual meth­ods i.e. meth­ods we can’t inter­cept. We’ll con­cen­trate on that method in the next part of the tutorial.

What we need right now is Should­In­ter­cept­Method method, that enables us to decide whether we want to inter­cept a method or not. We know all we need, so let’s add a new class to our project, and imple­ment the method, leav­ing two other meth­ods empty just for now.

public class FreezableProxyGenerationHook:IProxyGenerationHook
{
    public bool ShouldInterceptMethod(Type type, MethodInfo memberInfo)
    {
        return !memberInfo.Name.StartsWith("get_", StringComparison.Ordinal);
    }

    public void NonVirtualMemberNotification(Type type, MemberInfo memberInfo)
    {
    }

    public void MethodsInspected()
    {
    }
}

OK, we have our proxy gen­er­a­tion hook, but how do we actu­ally hook into the pipeline? To do this we need to pro­vide our hook to proxy gen­er­a­tion method, so a good place to look is Cre­ate­ClassProxy method on our gen­er­a­tor. Indeed it has quite a few overloads.

dptutorial_3_CreateClassProxyOverloads

Some of them take addi­tional para­me­ter of type Prox­y­Gen­er­a­tionOp­tions, and this is exactly what we need. We will exam­ine Prox­y­Gen­er­a­tionOp­tions class in details as we move through this tuto­r­ial, because it’s a very impor­tant class in advanced proxy related sce­nar­ios. Basi­cally, as its name implies it’s a con­tainer for var­i­ous options related to proxy gen­er­a­tion process. As such it’s also used to pass out proxy gen­er­a­tion hook to the proxy gen­er­a­tor. We can pass our Freez­ableProx­y­Gen­er­a­tionHook as a con­struc­tor argu­ment to Prox­y­Gen­er­a­tionOp­tions. We also need to cast gen­er­ated object back to its type,as we’re using a non-generic ver­sion of Cre­ate­ClassProxy now.

public static TFreezable MakeFreezable<TFreezable>() where TFreezable : class, new()
{
    var freezableInterceptor = new FreezableInterceptor();
    var options = new ProxyGenerationOptions(new FreezableProxyGenerationHook());
    var proxy = _generator.CreateClassProxy(typeof(TFreezable), options, new CallLoggingInterceptor(), freezableInterceptor);
    _freezables.Add(proxy, freezableInterceptor);
    return proxy as TFreezable;
}

With this change we can now see if our test passes…

dptutorial_3_getter_passed_test

…and it does, along with all other. Great. Now, our imple­men­ta­tion takes care of prop­erty get­ters but how about all the other meth­ods? Well they get inter­cepted too, which is not the desired behav­ior. To fix that, we need to slightly alter our Should­In­ter­cept­Method method to inter­cept only prop­erty setters

public bool ShouldInterceptMethod(Type type, MethodInfo memberInfo)
{
    return memberInfo.Name.StartsWith("set_", StringComparison.Ordinal);
}

(I did this in a test first man­ner, but I won’t bore you with screen­shots of failed/passed tests. The tests how­ever are in the attached code).

If we run the pro­gram now, we’ll see that only set­ters get intercepted.

dptutorial_3_program

In the next part of the tuto­r­ial we’ll take care of cases when we can’t ensure objects state is never changed, and we’ll dis­cuss the imple­men­ta­tion of Get­Inter­cept­ed­Meth­od­sCount­For method from our tests.

In the mean­time, if you have any ques­tions use the “leave com­ment” fea­ture of this blog, or ping me via GTalk, email or Skype.

Code is here.

Tech­no­rati Tags: , ,
  • Scott_M

    Can Dynam­icProxy used to inter­cept server side WCF objects?

    Dynam­icProxy appears to be more elo­quent that WCF MessageInspection

    weblogs.asp.net/…/…-wcf-message-inspector.aspx

  • http://kozmic.pl/Default.aspx Krzysztof Koźmic

    @Scott

    Yes, it can, but you'd have to use another WCF' exten­sion point — IIn­stan­ce­Provider (msdn.microsoft.com/…/…r.iinstanceprovider.aspx) to inject prox­ied instance into Wcf ServiceHost.

    You don't have to do it man­u­ally though. In Cas­tle project there is Wind­sor Facil­ity that inte­grates Wind­sor with WCF, and it uses Dynam­icProxy for a lot of things it is doing.

  • Miao Jack

    hello Krzysztof Koźmic,
    I have down­loaded the source of your arti­cles
    like "cid-6e18e107780d3f4a.skydrive.live.com/…/6E18!145", but I can­not upzip the files, can you give me a help?

  • Aalano

    hello, is there any planned sup­port to retain the method's para­me­ters' attrib­utes in proxies?

    We're using ASP.Net mvc over here with a CustomModelBinderAttribute.

  • http://kozmic.pl/Default.aspx Krzysztof Koźmic

    @Aalano

    DP does repli­cate all non-inheritable attrib­utes. So you should be able to access them on your prox­ies. If you're hav­ing prob­lems with that go to cas­tle users group on google groups and pro­vide some more details.