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

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

We’ll start by updating our CallLoggingInterceptor class, so that we can use it in tests. What we need from it, is to enhance its functionality, so that it not only logs (to the Console) the calls, but also keeps a count of calls. To do that we add a property called Count, which gets incremented each time a method is called. It’s a trivial code, so I’ won’t show it here.

With that, we can now write our first test. We want property setters to fire tests for object freezability. Getters are safe in this regard and test always passes, so it’s an unnecessary overhead. We want our getters to not get intercepted, 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 concern ourselves with GetInterceptedMethodsCountFor method just yet. For now let’s only say that it just does what its name suggests. We’ll inspect its implementation 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 mechanism that will enable us to choose, which methods we want to intercept, and which we don’t. Fortunately Dynamic Proxy enables that. It exposes a hook that enables us to plug in into its proxy generation pipeline, in form of… IProxyGenerationHook interface (you wouldn’t guess, would you?).

dptutorial_3_IProxyGenerationHook

Two really interesting methods are NonVirtualMemberNotification, that enables us to act, when user wants to proxy a type that has some non-virtual methods i.e. methods we can’t intercept. We’ll concentrate on that method in the next part of the tutorial.

What we need right now is ShouldInterceptMethod method, that enables us to decide whether we want to intercept a method or not. We know all we need, so let’s add a new class to our project, and implement the method, leaving two other methods 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 generation hook, but how do we actually hook into the pipeline? To do this we need to provide our hook to proxy generation method, so a good place to look is CreateClassProxy method on our generator. Indeed it has quite a few overloads.

dptutorial_3_CreateClassProxyOverloads

Some of them take additional parameter of type ProxyGenerationOptions, and this is exactly what we need. We will examine ProxyGenerationOptions class in details as we move through this tutorial, because it’s a very important class in advanced proxy related scenarios. Basically, as its name implies it’s a container for various options related to proxy generation process. As such it’s also used to pass out proxy generation hook to the proxy generator. We can pass our FreezableProxyGenerationHook as a constructor argument to ProxyGenerationOptions. We also need to cast generated object back to its type,as we’re using a non-generic version of CreateClassProxy 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 implementation takes care of property getters but how about all the other methods? Well they get intercepted too, which is not the desired behavior. To fix that, we need to slightly alter our ShouldInterceptMethod method to intercept only property setters

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

(I did this in a test first manner, but I won’t bore you with screenshots of failed/passed tests. The tests however are in the attached code).

If we run the program now, we’ll see that only setters get intercepted.

dptutorial_3_program

In the next part of the tutorial we’ll take care of cases when we can’t ensure objects state is never changed, and we’ll discuss the implementation of GetInterceptedMethodsCountFor method from our tests.

In the meantime, if you have any questions use the “leave comment” feature of this blog, or ping me via GTalk, email or Skype.

Code is here.

Technorati Tags: , ,

Comments

Scott_M says:

Can DynamicProxy used to intercept server side WCF objects?

DynamicProxy appears to be more eloquent that WCF MessageInspection

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

@Scott

Yes, it can, but you’d have to use another WCF’ extension point – IInstanceProvider (msdn.microsoft.com/…/…r.iinstanceprovider.aspx) to inject proxied instance into Wcf ServiceHost.

You don’t have to do it manually though. In Castle project there is Windsor Facility that integrates Windsor with WCF, and it uses DynamicProxy for a lot of things it is doing.

Miao Jack says:

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

Aalano says:

hello, is there any planned support to retain the method’s parameters’ attributes in proxies?

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

@Aalano

DP does replicate all non-inheritable attributes. So you should be able to access them on your proxies. If you’re having problems with that go to castle users group on google groups and provide some more details.