Upgrading to Windsor 2.5 (Northwind)

I was looking at Sharp Architecture project and as I went through the codebase (the sample application in particular) I found several spots that weren’t using Windsor in a optimal way, and few other that could really benefit from some of the new improvements in version 2.5. So instead of keeping that knowledge all to myself I though I might as well use it as an example and show the process of migration I went though with it. The guide is based on current source from SA repository, and its Northwind sample application.

So here we go

We start off by copying Windsor 2.5 binaries to bin folder of SharpArchitecture.

1_copy_binaries

Windsor 2.5 consists of only 2 assemblies, as compared to 4 in previous versions. Castle.MicroKernel.dll and Castle.DynamicProxy2.dll are no longer needed (the classes from these two assemblies were integrated into the two other assemblies).

2_remove_old_binaries

Since SharpArchitecture users NHibernate which has dependency on DynamicProxy I also needed to rebuild the NHibernate.ByteCode.Castle.dll for the new DynamicProxy (which now lives in Castle.Core.dll). It may seem complicated but really was just a matter of fixing some namespaces.

SharpArch project

Fixing Sharp Architecture was quite simple. I opened the solution, built it, watched it fail, and stared to fix references (removing references to defunct, Castle.DynamicProxy2.dll, and removing or replacing with Castle.Windsor.dll references to Castle.MicroKernel.dll)

Breaking change

While updating the code I also stumbled upon one breaking change in new Windsor.

3_breaking_change

 

 

ServiceSelector delegate (used in WithService.Select calls) changed signature so that its 2nd parameter is now an array of Types, not a single type. If you look at BreakingChanges.txt distributed with Windsor 2.5, you’ll find that it documents this breaking change along with suggestion how to upgrade your old code.

fix - depending on the scenario. You would either ignore it, or wrap your current method's body
    in foreach(var baseType in baseTypes)

In our case the former applies, so we just update the signature and move on. The project now compiles just fine.

Northwind project

With that done we can shift focus to the sample Northwind application. We don’t need to do anything other than upgrading references to Windsor, NHibernate bytecode provider and SharpArch to get it to compile. This does not mean that we’re done though.

Obsolete API

The project will compile but will give us 18 errors. That’s something most users upgrading older apps will see. If you take a look at the errors you’ll see something like this:

4_obsolete_api

In Windsor 2.5 all the old registration API (all AddComponent and friends methods) became obsolete, as first step towards cleaning the API of the container. All the obsolete methods points us towards alternative – supported API that we can use to achieve the same thing so we can quite easily migrate the old calls. We won’t follow the suggestions from the error messages. Instead we’ll take a step back, to look at how the registration is being done.

Installers

All the obsolete calls come from a single class – ComponentRegistrar, which looks like this:

public class ComponentRegistrar
{
    public static void AddComponentsTo(IWindsorContainer container)
    {
        AddGenericRepositoriesTo(container);
        AddCustomRepositoriesTo(container);
        AddApplicationServicesTo(container);
        AddWcfServiceFactoriesTo(container);
 
        container.AddComponent("validator",
                                typeof (IValidator), typeof (Validator));
    }
    
    // private registration methods
}

 

 

Windsor has (and had for a very long time) a better – “official” way of doing this, using installers. To take advantage of that we’ll start by moving code from the Add*To methods, to dedicated installer types.

For example we could take the AddGenericRepositoriesTo method

private static void AddGenericRepositoriesTo(IWindsorContainer container)
{
    container.AddComponent("repositoryType",
                            typeof (IRepository<>), typeof (Repository<>));
    container.AddComponent("nhibernateRepositoryType",
                            typeof (INHibernateRepository<>), typeof (NHibernateRepository<>));
    container.AddComponent("repositoryWithTypedId",
                            typeof (IRepositoryWithTypedId<,>), typeof (RepositoryWithTypedId<,>));
    container.AddComponent("nhibernateRepositoryWithTypedId",
                            typeof (INHibernateRepositoryWithTypedId<,>), typeof (NHibernateRepositoryWithTypedId<,>));
}

and extract it to an installer:

public class GenericRepositoriesInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Add(typeof (IRepository<>), typeof (Repository<>)),
            Add(typeof (INHibernateRepository<>), typeof (NHibernateRepository<>)),
            Add(typeof (IRepositoryWithTypedId<,>), typeof (RepositoryWithTypedId<,>)),
            Add(typeof (INHibernateRepositoryWithTypedId<,>), typeof (NHibernateRepositoryWithTypedId<,>)));
    }
 
    private IRegistration Add(Type service, Type implementation)
    {
        return Component.For(service).ImplementedBy(implementation);
    }
}

I dropped the name of the component since it’s never used in the application anyway, and I extracted common code to a helper method.

Factories and parameters

Let’s have a look at another one of these methods:

private static void AddWcfServiceFactoriesTo(IWindsorContainer container)
{
    container.AddFacility("factories", new FactorySupportFacility());
    container.AddComponent("standard.interceptor", typeof (StandardInterceptor));
 
    var factoryKey = "territoriesWcfServiceFactory";
    var serviceKey = "territoriesWcfService";
 
    container.AddComponent(factoryKey, typeof (TerritoriesWcfServiceFactory));
    var config = new MutableConfiguration(serviceKey);
    config.Attributes["factoryId"] = factoryKey;
    config.Attributes["factoryCreate"] = "Create";
    container.Kernel.ConfigurationStore.AddComponentConfiguration(serviceKey, config);
    container.Kernel.AddComponent(serviceKey, typeof (ITerritoriesWcfService),
                                    typeof (TerritoriesWcfServiceClient), LifestyleType.PerWebRequest);
}

 

 

There’s quite a lot going on here. It’s using FactorySupportFacility to register a service as well as a factory that will provide instances of this service. Why does it use a factory?

public class TerritoriesWcfServiceFactory
{
    public ITerritoriesWcfService Create()
    {
        var address = new EndpointAddress(
            // I see the below as a magic string; I typically like to move these to a 
            // web.config reader to consolidate the app setting names
            ConfigurationManager.AppSettings["territoryWcfServiceUri"]);
        var binding = new WSHttpBinding();
 
        return new TerritoriesWcfServiceClient(binding, address);
    }
}

The factory provides arguments to the service, and one of these arguments is also depending on a value from the config file. How can we do this better? We have two options here. We either register the binding and endpoint address in our container as services, so that Windsor itself can provide them to the TerritoriesWcfServiceClient or we register them as inline dependencies of the service. I don’t think it makes much sense to register them as services, so we’ll go with the latter option.

public class TerritoriesWcfServiceClientInstaller:IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        var address = new EndpointAddress(ConfigurationManager.AppSettings["territoryWcfServiceUri"]);
        container.Register(Component.For<ITerritoriesWcfService>()
                            .ImplementedBy<TerritoriesWcfServiceClient>()
                            .DependsOn(Property.ForKey<Binding>().Eq(new WSHttpBinding()),
                                           Property.ForKey<EndpointAddress>().Eq(address))
                            .LifeStyle.PerWebRequest);
    }
}

 

We’re not using a factory here, letting Windsor create the instance for us. We’re also passing the binding and address as typed dependencies, which is a new option in version 2.5.

The last method I want to look at (as this post is already enormously big) is this:

private static void AddApplicationServicesTo(IWindsorContainer container)
{
    container.Register(
        AllTypes.Pick()
            .FromAssemblyNamed("Northwind.ApplicationServices")
            .WithService.FirstInterface());
}

 

There are two issues with it. First it calls Pick() before FromAssembly*. That’s not a big deal but in Windsor 2.5 we tried to unify the API so that the order always should be: Specify assembly –> specify components –> configure.

The other issue is that it uses FirstInterface to pick a service for a type. Problem with that is, that if type implements more than one interface, which one is “first” is undefined. It can be one on Thursdays, and the other one on Fridays. Good luck chasing issues caused by this.

Default service

Windsor 2.5 adds new option that first the purpose much better in this case – default interface. It performs matching based on type/interface name. Since we have actually just single class and interface in that assembly: DashboardService/IDashboardService they are perfect match for this. So that our installer would look like this:

public class ApplicationServicesInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            AllTypes.FromAssemblyNamed("Northwind.ApplicationServices")
            .Pick().WithService.DefaultInterface());
    }
}

 

Installing the installers

Now having all registration enclosed in installers in our project we can change this:

ComponentRegistrar.AddComponentsTo(container);

 

to this:

container.Install(FromAssembly.This());

and Windsor will take care of all the rest.

In closing

That’s pretty much all it takes to upgrade the app to run on top of latest and greatest version of Windsor. In addition we introduced some new features that you likely are going to take advantage of when upgrading your apps (or starting new ones as well). I suspect there is some room for improvement in the Northwind app, and place for some other Windsor features but that perhaps should be left for another post.


Posted 08-24-2010 3:31 PM by Krzysztof Koźmic
Filed under: ,

[Advertisement]

Comments

JohnE wrote re: Upgrading to Windsor 2.5 (Northwind)
on 08-25-2010 8:00 AM

Thanks for the article.  

Re:

"Windsor has (and had for a very long time) a better – “official” way of doing this, using installers. To take advantage of that we’ll start by moving code from the Add*To methods, to dedicated installer types."

See:

www.castleproject.org/.../Generated_IWindsorContainer.html

Alex wrote re: Upgrading to Windsor 2.5 (Northwind)
on 08-25-2010 10:02 AM

good work... i will surely use your article to update my code and see if i do everything the right way

Tim Barcz wrote re: Upgrading to Windsor 2.5 (Northwind)
on 08-27-2010 10:12 AM

I like some of the changes you've made in Windsor - some in previous versions and some in 2.5 - but we're largely getting them all now because of the upgrade.

Very thorough and well done. Castle is in good hands.

Tim

PandaWood wrote re: Upgrading to Windsor 2.5 (Northwind)
on 07-12-2011 2:20 AM

Thanks.

I don't quite understand the first error's explanation, as I have the same code from Kyle's blog post (codebetter.com/.../auto-registration-in-asp-net-mvc)

And the error here is the same as the 2nd error you have about IEnumerable<System.Type>

But the problem is with the return type, such that the code below doesn't compile because of the return value.

And my knowledge of C# is obviously crap as I can't yet get this code (below) to compile under the new changes :-(

_container.Register(

   AllTypes.Pick( )

       .FromAssemblyNamed( "Trilogy.Gunton.DataAccess" )

       .WithService.Select(

       delegate( Type type )

           {

               var interfaces = type.GetInterfaces( )

                   .Where(

                   t => t.IsGenericType == false && t.Namespace.StartsWith( "Trilogy.Gunton" )

                   );

               if ( interfaces.Count( ) > 0 )

               {

                   return interfaces.ElementAt( 0 );

               }

               return null;

           }

       )

   );

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)