Although Microsoft delivered Composite WPF with the unity container it is possible to use a different IoC container. In this post I will explain how it can be done and what caveats you are going to encounter when replacing the IoC container in Composite WPF with Spring.NET
Where to start
Replacing the IoC container is somewhat more advanced than just working with Composite WPF and requires some insight into the internals of Composite WPF that you otherwise would leave alone. The component you need to know more about is the UnityBootstrapper. All the logic required to get a composite WPF application running is placed there.
What this class does is create the unity container for the application, configure all the extensions and types required and run a simple process to get the application running. The process that gets the application running looks like this:
- Create the IoC container
- Configure the IoC container
- Configure the region adapter mappings
- Create the shell window
- Initialize the modules
This process will need to be reproduced in the new spring bootstrapper.
Step 1 and 2: Spring Container configuration
The first step is to setup a basic configuration of the Spring.NET IoC container so we can use this configuration later on when performing step 1 and 2 in the bootstrapping process. Composite WPF requires the following components to be present:
- IRegionManager - The region manager for the shell window
- IEventAggregator - Event aggregator component
- ILoggingFacade - Wrapper interface for the used logging component
- IContainerFacade - This is the IoC container itself
- RegionAdapterMappings - Contains the adapters required to allow modules to show views on various regions
To make things easy I've named the dependencies exactly as presented in the list above. So when working with the implementation provided in CompositeWPFContrib you will need those. After completing the configuration it will look like this:
4     <resource uri="config://spring/objects"/>
7   <!-- The following objects are required by default -->
8   <objects >="http://www.springframework.net">
9     <object id="RegionAdapterMappings"
10             type="Microsoft.Practices.Composite.Wpf.Regions.RegionAdapterMappings, Microsoft.Practices.Composite.Wpf"
12     <object id="IRegionManager"
13             type="Microsoft.Practices.Composite.Wpf.Regions.RegionManager, Microsoft.Practices.Composite.Wpf"
15       <constructor-arg name="mappings" ref="RegionAdapterMappings"/>
17     <object id="IEventAggregator"
18             type="Microsoft.Practices.Composite.Events.EventAggregator, Microsoft.Practices.Composite"
20     <object id="IModuleLoader"
21             type="Microsoft.Practices.Composite.Modularity.ModuleLoader, Microsoft.Practices.Composite"
23       <constructor-arg name="loggerFacade" ref="ILoggerFacade"/>
24       <constructor-arg name="containerFacade" ref="IContainerFacade"/>
26     <object id="ILoggerFacade"
27             type="Microsoft.Practices.Composite.Logging.TraceLogger, Microsoft.Practices.Composite"
29     <object id="IContainerFacade"
30             type="CompositeWPFContrib.Composite.SpringExtensions.SpringContainerAdapter, CompositeWPFContrib.Composite.SpringExtensions"
With the configuration in place step 1 and 2 are in place. Yes you are right, I didn't talk about how to create the container. Trust me it's really simple, the line of code required is this: ContextRegistry.GetContext(); and as long as the application has an app.config file with the Spring configuration and a root context it all works out.
Step 3: Configuring the region adapter mappings
The third step is configuring the region adapter mappings. By default there are three region adapters mapped. These will provide developers with a broad range of possible regions, but in case you need more you can always override the ConfigureRegionAdapterMappings method and add your own.
The code required to configure the region adapter mappings looks like this:
1 /// <summary>
2 /// Configures the default region adapter mappings to use in the application, in order
3 /// to adapt UI controls defined in XAML to use a region and register it automatically.
4 /// May be overwritten in a derived class to add specific mappings required by the application.
5 /// </summary>
6 /// <returns>The <see cref="RegionAdapterMappings"/> instance containing all the mappings.</returns>
7 protected virtual RegionAdapterMappings ConfigureRegionAdapterMappings()
9     RegionAdapterMappings regionAdapterMappings = (RegionAdapterMappings)_container.GetObject("RegionAdapterMappings");
11     if (regionAdapterMappings != null)
13         regionAdapterMappings.RegisterMapping(typeof(Selector), new SelectorRegionAdapter());
14         regionAdapterMappings.RegisterMapping(typeof(ItemsControl), new ItemsControlRegionAdapter());
15         regionAdapterMappings.RegisterMapping(typeof(ContentControl), new ContentControlRegionAdapter());
18     return regionAdapterMappings;
Step 4: Creating the shell window
The 4th step in the bootstrapping process can't be done using XML configuration and requires a little help from the developer. In the CreateShell method a new instance of the shell window should be created and returned to the bootstrapper. After that the bootstrapper will assign the IRegionManager that was configured previously to the shell window.
Step 5: Initializing the modules
The last step in the bootstrapping process is initializing the modules required by the application. This is done by executing the following piece of code:
1 /// <summary>
2 /// Initializes the modules. May be overwritten in a derived class to use custom
3 /// module loading and avoid using an <seealso cref="IModuleLoader"/> and
4 /// <seealso cref="IModuleEnumerator"/>.
5 /// </summary>
6 protected virtual void InitializeModules()
8     IModuleEnumerator moduleEnumerator = (IModuleEnumerator)_container.GetObject("IModuleEnumerator");
10     if (moduleEnumerator == null)
12         throw new InvalidOperationException(
16     IModuleLoader moduleLoader = (IModuleLoader)_container.GetObject("IModuleLoader");
17     if (moduleLoader == null)
19         throw new InvalidOperationException(
23     ModuleInfo moduleInfo = moduleEnumerator.GetStartupLoadedModules();
The observant reader may notice that this method is virtual. This can be very useful to developers who would like to use the extended module loader service that I talked about previously or to developers who want to use the .NET add-in framework instead of the default implementation. The default implementation works great, but doesn't isolate the modules from the shell and because of that you can't update them while the application is running.
Points of interest
Lots of people including myself use the IUnityContainer interface to get a reference to the IoC container, which isn't a really good practice when you want your application to for example use Spring.NET. Instead of using the IUnityContainer interface I recommend the use of IContainerFacade, because that way you can always replace the IoC container.
As Composite WPF does not make any assumptions about the IoC container being used it is pretty easy to replace the container. However be aware that most if not all extensions on CompositeWPF are build with the Unity container in mind and may not be 100% compatible with this bootstrapper.
Why not to use this approach
You might be wondering why I put in this section, why wouldn't you want to replace the default IoC container in Composite WPF? There are a couple of reasons why you should avoid using this container:
- Spring.NET is extremely dependend on configuration
Every single object needs to be registered with the application context and without configuring a dependency it simply isn't injected. This can cause major issues when you fail to configure the components right and you will most likely end up with an non-operational application. And because I was unable to do the configuration from code and let the modules use that, I am bound to XML which can be edited by users (And be broken by them too).
- Spring is too explicit
This is closely related to the dependency on configuration. When you need an object you will have to request it by name. If you don't know the name of the object you get nothing. It would be way better if I could resolve and object by type preferably using generics. That way I get the type check by the compiler for free and don't have to cast the object once I got it. Spring.NET doesn't use this principle and is as such not that friendly to developers. I dont like it anno 2008 and as there's an alternative, I will stick with that.
- The default is great
This last argument is kind of lame, but a very good and especially cheap solution for a customer. Why replace stuff when the default is great? Replacing the IoC container requires testing the replacement and above all costs time to do the actual replacement.
I see this bootstrapper as a good check if Microsoft did indeed fulfill their promise of a framework that is extensible when it comes to some of the core components. Although I wouldn't recommend to anyone to replace the default IoC container in Composite WPF. It is there if you require the use of Spring.NET, but I cannot urge people enough to check out Unity for their other IoC needs, because it is in my opinion more solid when you look at the configuration of the container.
The sourcecode of the bootstrapper can be downloaded from http://www.codeplex.com/compositewpfcontrib