Overriding generic component’s resolution in Castle Windsor

Few months ago, a user asked the following question on the Castle users discussion group. A friend asked me about the same thing today, so I thought I’d blog this so that’s easier to find than the discussion group thread. Anyway, here’s the question:

say I have the following types

public interface ISometype<T> {} 
public class SomeTypeImpl<T>:ISometype<T> {} 
public class SomeSpecificTypeImpl<T>:ISometype<T> where T: ISomeSpecificSpecifier {} 
public interface ISomeSpecificSpecifier { } 

Is there a way to register ISomeType<> in the container so that for the general case it uses SomeTypeImpl<> but for cases where T is ISomeSpecificSpecifier it could use SomeSpecificTypeImpl<>?

Solution take one – the explicit version

The solution may be similar to the following sketchy code.

class Program
{
    static void Main(string[] args)
    {
        var container = new WindsorContainer();
        container.Register(Component.For(typeof(ISometype<>)).ImplementedBy(typeof(SomeTypeImpl<>)),
                           Component.For(typeof(ISometype<>)).ImplementedBy(typeof(SomeSpecificTypeImpl<>))
                               .PreferredFor<ISomeSpecificSpecifier>());
        var selector = new Selector();
        container.Kernel.AddHandlerSelector(selector);
 
        var sometype = container.Resolve<ISometype<string>>();
        Debug.Assert(sometype.GetType() == typeof(SomeTypeImpl<string>));
 
        var sometype2 = container.Resolve<ISometype<Specifier>>();
        Debug.Assert(sometype2.GetType() == typeof(SomeSpecificTypeImpl<Specifier>));
    }
}
 
public static class Preferred
{
    public static ComponentRegistration<object> PreferredFor<TClosingGenericType>(this ComponentRegistration<object> registration)
    {
        return registration.ExtendedProperties(new Preference(typeof(TClosingGenericType)));
    }
}
 
internal class Preference
{
    public static readonly string Name = "PreferredForClosingType";
    public Type PreferredForClosingType { get; set; }
 
    public Preference(Type type)
    {
        this.PreferredForClosingType = type;
    }
}
 
public class Selector : IHandlerSelector
{
    public bool HasOpinionAbout(string key, Type service)
    {
        // that's about as much as we can say at this point...
        return service.IsGenericType && service.GetGenericArguments().Length == 1;
    }
 
    public IHandler SelectHandler(string key, Type service, IHandler[] handlers)
    {
        var @default = handlers.FirstOrDefault(h => MatchHandler(service, h));
        return @default ?? handlers.First();
    }
 
    private bool MatchHandler(Type service, IHandler handler)
    {
        if (handler.ComponentModel.ExtendedProperties.Contains(Preference.Name) == false)
            return false;
        var closingTypeRequired = (Type)handler.ComponentModel.ExtendedProperties[Preference.Name];
        var closingTypeActual = service.GetGenericArguments().Single();
        return closingTypeRequired.IsAssignableFrom(closingTypeActual);
    }
}

This version contains an explicit extension to the component registration API, to specify which component you prefer in which case. It also uses and IHandlerSelector implementation to do the actual work during resolution. It is possible however to not extend the API, and use the information we already have

Solution take two – the implicit version

class Program
{
    static void Main(string[] args)
    {
        var container = new WindsorContainer();
        container.Register(Component.For(typeof(ISometype<>)).ImplementedBy(typeof(SomeTypeImpl<>)),
                           Component.For(typeof(ISometype<>)).ImplementedBy(typeof(SomeSpecificTypeImpl<>)));
        var selector = new Selector();
        container.Kernel.AddHandlerSelector(selector);
 
        var sometype = container.Resolve<ISometype<string>>();
        Debug.Assert(sometype.GetType() == typeof(SomeTypeImpl<string>));
 
        var sometype2 = container.Resolve<ISometype<Specifier>>();
        Debug.Assert(sometype2.GetType() == typeof(SomeSpecificTypeImpl<Specifier>));
    }
}
 
public class Selector : IHandlerSelector
{
    public bool HasOpinionAbout(string key, Type service)
    {
        // that's about as much as we can say at this point...
        return service.IsGenericType && service.GetGenericArguments().Length == 1;
    }
 
    public IHandler SelectHandler(string key, Type service, IHandler[] handlers)
    {
        return handlers.FirstOrDefault(h => MatchHandler(service, h));
 
    }
 
    private bool MatchHandler(Type service, IHandler handler)
    {
        var closingTypeRequired = handler.ComponentModel.Implementation.GetGenericArguments()
            .Single().GetGenericParameterConstraints().SingleOrDefault();
        if (closingTypeRequired == null)
            return false;
        var closingTypeActual = service.GetGenericArguments().Single();
        return closingTypeRequired.IsAssignableFrom(closingTypeActual);
    }
}

This version relies on the information provided in component’s generic type constraints. The Registration API is not extended, and we just do a little bit more work in the selector. Is this version better? It depends. The explicit version is… well – explicit about what it does, and for which components. The implicit one works on its own and if you’re not careful you may end up chasing very strange bugs in your code. That said, if you do go for the second option, be sure to at least add some diagnostics logging to it, so that you can see what it does when you’re not watching.

Technorati Tags:

Posted 12-07-2009 11:28 PM by Krzysztof Koźmic
Filed under:

[Advertisement]

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)