Castle Windsor forwarded types and proxies

Castle Windsor allows you to use single component for multiple services, which is called Forwarded Types.

Forwarded Types

In other words, you can tell Windsor – when IFoo is requested use FooBar as implementation, and when Bar is requested also use FooBar (when using default lifestyle of singleton you’ll get the same instance).

Here’s some code:

var container = new WindsorContainer();
container.Register(Component.For<Bar>().Forward<IFoo>()
                       .ImplementedBy<FooBar>());
var foo = container.Resolve<IFoo>();
var bar = container.Resolve<Bar>();
Debug.Assert(foo == bar);
foo.DoFoo();
bar.DoBar();
Console.ReadKey(true);

Proxies

What if you want to use proxies for that component though?

var container = new WindsorContainer();
container.Register(Component.For<IInterceptor>()
                       .Named("interceptor")
                       .ImplementedBy<FooBarInterceptor>(),
                   Component.For<bar>().Forward<ifoo>()
                       .ImplementedBy<FooBar>()
                       .Interceptors(new InterceptorReference("Interceptor")).Anywhere);
var foo = container.Resolve<IFoo>();
var bar = container.Resolve<Bar>();
Debug.Assert(foo == bar);
foo.DoFoo();
bar.DoBar();
Console.ReadKey(true);

Now, what happens next depends on how you implemented the interface IFoo on class FooBar. Say this is FooBar:

public class FooBar : Bar, IFoo
{
    public void DoFoo()
    {
        Console.WriteLine("DoFoo");
    }
 
    public override void DoBar()
    {
        Console.WriteLine("DoBar");
    }
}

Notice that DoFoo is non-virtual. In this case, here’s what we’re gonna get.

windsor_1

DoFoo did not get intercepted. So what’s the issue here, and how do we fix it?

The What

When you forward a registration, Windsor runs just the first type through the complete registration pipeline, and subsequent forwarded types are treated just as additional piece of data “Oh, by the way, use this component I just registered for this type as well". Proxy registration is a part of the component model building, and since we end up having just one component only information about its main type gets recorded for proxying.

While this might appear at first clearly as a bug, I think it’s rather a by-design feature. Forcing Windsor to figure it out by itself could pretty quickly become very tricky and we might not always get what we expected. There are however ways of getting what we want.

The fix no 1

Now that we know what we’re up against, how do we fix it? First and the most trivial fix would be simply to make the DoFoo virtual – this way it would get picked when proxying Bar base class and we could successfully proxy it. While this may not always be applicable (you may not be able to modify the class) this is the only option that is available if you’re using the released version. However if you’re using trunk there are two more possible ways of bending it to our will.

Meet my attributes

Due to some changes in how Dynamic Proxy 2.2 (current trunk) handles additional interfaces to proxy, it is possible to intercept non-virtually implemented interface members on a class proxy. Since Windsor by default will request just the class proxy (with no additional interfaces) we need to tell it to toss an IFoo attribute in as well. The quickest way of doing it is throwing an attribute on top of our class:

[ComponentProxyBehavior(AdditionalInterfaces = new[] { typeof(IFoo) })]
public class FooBar : Bar, IFoo
{
    public void DoFoo()
    {
        Console.WriteLine("DoFoo");
    }
 
    public override void DoBar()
    {
        Console.WriteLine("DoBar");
    }
}

If we run the code now we’ll get this:

windsor_2

This is not a solution most people would choose anyway. Even if you can do it (your service does not come from a 3rd party library), you’re decorating your service class with a Windsor-specific attribute which many consider an anti-pattern. There’s however a third, more pure way.

Remember that you need trunk version of Dynamic Proxy for this to work. If you use version 2.1 you’ll end up with this instead:

windsor_3

Windsor will implement the interface, but it will treat it as interface proxy without target. You can make it work by inserting a dedicated interceptor that will forward the calls to your class, but it’s something you’ll have to do manually all the way through.

Black belt approach

I said that forwarded types don’t get ran through whole registration pipeline. However, kernel does raise its component lifecycle events for them, so we can hook up to them and get notified when our forwarding handler gets registered, and modify its component model.

We start by hooking up to kernels HandlerRegistered event, before we register any components.

var container = new WindsorContainer();
container.Kernel.HandlerRegistered += OnHandlerRegistered;

 

In the OnHandlerRegistered method we check whether the handler at hand is a ForwardingHandler and if so we add its interface to the list of additional interfaces we want to proxy, just like we did using attribute in the example above.

static void OnHandlerRegistered(Castle.MicroKernel.IHandler handler, ref bool stateChanged)
{
    var forwardingHandler = handler as ForwardingHandler;
 
    if (forwardingHandler == null)
        return; //we're only interested in forwarding handlers...
 
    if(!forwardingHandler.Service.IsInterface)
        return; //we're only interested in interface services...
 
    var targetHandler = forwardingHandler.Target;
    if(!targetHandler.ComponentModel.ExtendedProperties.Contains(ProxyConstants.ProxyOptionsKey))
        return; //apparently this service is not registered for proxying
 
    var options = targetHandler.ComponentModel.ExtendedProperties[ProxyConstants.ProxyOptionsKey] as ProxyOptions;
    Debug.Assert(options != null);
 
    options.AddAdditionalInterfaces(forwardingHandler.Service);
}

Now we get our interface proxied without having to touch our component’ code.

Technorati Tags:

Posted 10-22-2009 9:41 PM by Krzysztof Koźmic
Filed under:

[Advertisement]

Comments

Rob Reynolds wrote re: Castle Windsor forwarded types and proxies
on 12-04-2009 2:41 PM

This is sweet! Very helpful for what I was trying to do with interceptors. I was missing the Component.For<Implementation>.Forward<Interface>.ImplementedBy<Implementation>

This was what I needed. :D

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)