For a large, enterprise application at work, I have been utilizing the Prism (CompositeWPF) framework. The application is still in progress, so I am unable to discuss it’s nature too much in detail. However, Prism has been a large help in getting the base framework for the application laid out and constructed. After finishing a few other tasks, I decided to check the status of the CompositeWPF project and look for any updates. That day just happened to coincide with Prism2′s latest release. The update sounded appealing, so I started the process of moving everything over to it.
While moving over to the February 2009 release of Prism2, I ran into issues with a few of the pieces I was using from the CompositeWPFContrib project. For example, the Windsor container bootstrapper had not been updated for the new release of Prism, leading to many problems. In order to fully move over to Prism2, I’d need to either give up on using Windsor, or create a new bootstrapper for Prism2 capable of working with Castle Windsor. Choosing option 2, I started with the bootstrapper for Unity and with the base Castle Windsor implementation for ServiceLocator, then modified them quite a bit. The final results that I ended up with will be posted below.
One significant difference between Castle Windsor and Unity came to light while writing and testing these new classes. If a specific instance of a class that hasn’t been registered into the container is requested from Unity, Unity doesn’t complain and just goes ahead and injects what is needed. Castle Windsor, on the other hand, will throw an exception complaining that the component wasn’t registered. Both seem like valid approaches, but I wanted to make the transition as simple as possible. So, to avoid changing too much of the logic found within Prism2 (and the bootstrapper), I added a “ResolveEx” extension function to IWindsorContainer. If a specific class is requested through this function that isn’t in the container, it will register it as transient and return an instance. I also changed the ServiceLocator implementation for Castle Windsor to use “ResolveEx” if a key is not passed in.
And without further ado, here are the files in all their glory (+ zip):
WindsorBootstrapper.cs
Note for WindsorBootstrapper.cs: There are four resource properties that will need to be added to your project. They should just be simple strings that explain the problem that occurred. The four resource properties are: NullLoggerFacadeException, NullWindsorContainerException, NullModuleCatalogException, and TypeMappingAlreadyRegistered.
#region Usings! using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using Core.Properties; using Castle.MicroKernel; using Castle.Windsor; using Microsoft.Practices.Composite; using Microsoft.Practices.Composite.Events; using Microsoft.Practices.Composite.Logging; using Microsoft.Practices.Composite.Modularity; using Microsoft.Practices.Composite.Presentation.Regions; using Microsoft.Practices.Composite.Presentation.Regions.Behaviors; using Microsoft.Practices.Composite.Regions; using Microsoft.Practices.ServiceLocation; #endregion namespace Core.Windsor { /// <summary> /// Base class that provides a basic bootstrapping sequence that /// registers most of the Composite Application Library assets /// in a <see cref="IWindsorContainer"/> and uses <see cref="Log4NetLogger" /> /// for logging. /// </summary> /// <remarks> /// This class must be overriden to provide application specific configuration. /// </remarks> public abstract class WindsorBootstrapper { private readonly ILoggerFacade loggerFacade = new Log4NetLogger(); private bool useDefaultConfiguration = true; /// <summary> /// Gets the default <see cref="IWindsorContainer"/> for the application. /// </summary> /// <value>The default <see cref="IWindsorContainer"/> instance.</value> public IWindsorContainer Container { get; private set; } /// <summary> /// Gets the default <see cref="ILoggerFacade"/> for the application. /// </summary> /// <value>A <see cref="ILoggerFacade"/> instance.</value> protected virtual ILoggerFacade LoggerFacade { get { return loggerFacade; } } /// <summary> /// Runs the bootstrapper process. /// </summary> public void Run() { Run(true); } /// <summary> /// Run the bootstrapper process. /// </summary> /// <param name="runWithDefaultConfiguration">If <see langword="true"/>, registers default Composite Application Library services in the container. This is the default behavior.</param> public void Run(bool runWithDefaultConfiguration) { useDefaultConfiguration = runWithDefaultConfiguration; ILoggerFacade logger = LoggerFacade; if (logger == null) { throw new InvalidOperationException(Resources.NullLoggerFacadeException); } logger.Log("Creating Windsor container", Category.Debug, Priority.Low); Container = CreateContainer(); if (Container == null) { throw new InvalidOperationException(Resources.NullWindsorContainerException); } logger.Log("Configuring container", Category.Debug, Priority.Low); ConfigureContainer(); logger.Log("Configuring region adapters", Category.Debug, Priority.Low); ConfigureRegionAdapterMappings(); ConfigureDefaultRegionBehaviors(); RegisterFrameworkExceptionTypes(); logger.Log("Creating shell", Category.Debug, Priority.Low); DependencyObject shell = CreateShell(); if (shell != null) { RegionManager.SetRegionManager(shell, Container.Resolve<IRegionManager>()); RegionManager.UpdateRegions(); } logger.Log("Initializing modules", Category.Debug, Priority.Low); InitializeModules(); logger.Log("Bootstrapper sequence completed", Category.Debug, Priority.Low); } /// <summary> /// Registers in the <see cref="IWindsorContainer"/> the <see cref="Type"/> of the Exceptions /// that are not considered root exceptions by the <see cref="ExceptionExtensions"/>. /// </summary> protected virtual void RegisterFrameworkExceptionTypes() { ExceptionExtensions.RegisterFrameworkExceptionType( typeof (ActivationException)); ExceptionExtensions.RegisterFrameworkExceptionType( typeof (ComponentNotFoundException)); ExceptionExtensions.RegisterFrameworkExceptionType( typeof (ComponentRegistrationException)); } /// <summary> /// Configures the <see cref="IWindsorContainer"/>. May be overwritten in a derived class to add specific /// type mappings required by the application. /// </summary> protected virtual void ConfigureContainer() { Container.RegisterInstance(LoggerFacade); Container.RegisterInstance(Container); IModuleCatalog catalog = GetModuleCatalog(); if (catalog != null) { Container.RegisterInstance(catalog); } if (useDefaultConfiguration) { RegisterTypeIfMissing<IServiceLocator, WindsorServiceLocator>(true); RegisterTypeIfMissing<IModuleInitializer, ModuleInitializer>(true); RegisterTypeIfMissing<IModuleManager, ModuleManager>(true); RegisterTypeIfMissing<RegionAdapterMappings, RegionAdapterMappings>(true); RegisterTypeIfMissing<IRegionManager, RegionManager>(true); RegisterTypeIfMissing<IEventAggregator, EventAggregator>(true); RegisterTypeIfMissing<IRegionViewRegistry, RegionViewRegistry>(true); RegisterTypeIfMissing<IRegionBehaviorFactory, RegionBehaviorFactory>(true); ServiceLocator.SetLocatorProvider(() => Container.Resolve<IServiceLocator>()); } } /// <summary> /// Configures the default region adapter mappings to use in the application, in order /// to adapt UI controls defined in XAML to use a region and register it automatically. /// May be overwritten in a derived class to add specific mappings required by the application. /// </summary> /// <returns>The <see cref="RegionAdapterMappings"/> instance containing all the mappings.</returns> protected virtual RegionAdapterMappings ConfigureRegionAdapterMappings() { var regionAdapterMappings = ServiceLocator.Current.GetInstance<RegionAdapterMappings>(); if (regionAdapterMappings != null) { #if SILVERLIGHT regionAdapterMappings.RegisterMapping(typeof(TabControl), ServiceLocator.Current.GetInstance<TabControlRegionAdapter>()); #endif regionAdapterMappings.RegisterMapping(typeof (Selector), ServiceLocator.Current.GetInstance<SelectorRegionAdapter>()); regionAdapterMappings.RegisterMapping(typeof (ItemsControl), ServiceLocator.Current.GetInstance<ItemsControlRegionAdapter>()); regionAdapterMappings.RegisterMapping(typeof (ContentControl), ServiceLocator.Current.GetInstance<ContentControlRegionAdapter>()); } return regionAdapterMappings; } /// <summary> /// Configures the <see cref="IRegionBehaviorFactory"/>. This will be the list of default /// behaviors that will be added to a region. /// </summary> protected virtual IRegionBehaviorFactory ConfigureDefaultRegionBehaviors() { var defaultRegionBehaviorTypesDictionary = Container.TryResolve<IRegionBehaviorFactory>(); if (defaultRegionBehaviorTypesDictionary != null) { defaultRegionBehaviorTypesDictionary.AddIfMissing(AutoPopulateRegionBehavior.BehaviorKey, typeof (AutoPopulateRegionBehavior)); defaultRegionBehaviorTypesDictionary.AddIfMissing( BindRegionContextToDependencyObjectBehavior.BehaviorKey, typeof (BindRegionContextToDependencyObjectBehavior)); defaultRegionBehaviorTypesDictionary.AddIfMissing(RegionActiveAwareBehavior.BehaviorKey, typeof (RegionActiveAwareBehavior)); defaultRegionBehaviorTypesDictionary.AddIfMissing(SyncRegionContextWithHostBehavior.BehaviorKey, typeof (SyncRegionContextWithHostBehavior)); defaultRegionBehaviorTypesDictionary.AddIfMissing(RegionManagerRegistrationBehavior.BehaviorKey, typeof (RegionManagerRegistrationBehavior)); } return defaultRegionBehaviorTypesDictionary; } /// <summary> /// Initializes the modules. May be overwritten in a derived class to use a custom Modules Catalog /// </summary> protected virtual void InitializeModules() { IModuleManager manager; try { manager = Container.Resolve<IModuleManager>(); } catch (ComponentNotFoundException ex) { if (ex.Message.Contains("IModuleCatalog")) { throw new InvalidOperationException(Resources.NullModuleCatalogException); } throw; } manager.Run(); } /// <summary> /// Returns the module catalog that will be used to initialize the modules. /// </summary> /// <remarks> /// When using the default initialization behavior, this method must be overwritten by a derived class. /// </remarks> /// <returns>An instance of <see cref="IModuleCatalog"/> that will be used to initialize the modules.</returns> [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] protected virtual IModuleCatalog GetModuleCatalog() { return null; } /// <summary> /// Creates the <see cref="IWindsorContainer"/> that will be used as the default container. /// </summary> /// <returns>A new instance of <see cref="IWindsorContainer"/>.</returns> protected virtual IWindsorContainer CreateContainer() { return new WindsorContainer(); } /// <summary> /// Registers a type in the container only if that type was not already registered. /// </summary> /// <param name="registerAsSingleton">Registers the type as a singleton.</param> protected void RegisterTypeIfMissing<TFromType, TToType>(bool registerAsSingleton) { ILoggerFacade logger = LoggerFacade; if (Container.IsTypeRegistered<TFromType>()) { logger.Log( String.Format(CultureInfo.CurrentCulture, Resources.TypeMappingAlreadyRegistered, typeof (TFromType).Name), Category.Debug, Priority.Low); } else { Container.RegisterType<TFromType, TToType>(registerAsSingleton); } } /// <summary> /// Creates the shell or main window of the application. /// </summary> /// <returns>The shell of the application.</returns> /// <remarks> /// If the returned instance is a <see cref="DependencyObject"/>, the /// <see cref="IWindsorContainer"/> will attach the default <seealso cref="IRegionManager"/> of /// the application in its <see cref="RegionManager.RegionManagerProperty"/> attached property /// in order to be able to add regions by using the <seealso cref="RegionManager.RegionNameProperty"/> /// attached property from XAML. /// </remarks> protected abstract DependencyObject CreateShell(); } }
WindsorContainerHelper.cs
#region Usings! using System; using System.Diagnostics.CodeAnalysis; using Castle.Core; using Castle.Windsor; #endregion namespace Core.Windsor { /// <summary> /// Extensions methods to extend and facilitate the usage of <see cref="IWindsorContainer"/>. /// </summary> public static class WindsorContainerHelper { /// <summary> /// Returns whether a specified type has a type mapping registered in the container. /// </summary> /// <param name="container">The <see cref="IWindsorContainer"/> to check for the type mapping.</param> /// <param name="type">The type to check if there is a type mapping for.</param> /// <returns><see langword="true"/> if there is a type mapping registered for <paramref name="type"/>.</returns> /// <remarks>In order to use this extension method, you first need to add the /// </remarks> public static bool IsTypeRegistered(this IWindsorContainer container, Type type) { return container.Kernel.HasComponent(type); } public static bool IsTypeRegistered<TType>(this IWindsorContainer container) { Type typeToCheck = typeof(TType); return IsTypeRegistered(container, typeToCheck); } /// <summary> /// Utility method to try to resolve a service from the container avoiding an exception if the container cannot build the type. /// </summary> /// <param name="container">The cointainer that will be used to resolve the type.</param> /// <typeparam name="T">The type to resolve.</typeparam> /// <returns>The instance of <typeparamref name="T"/> built up by the container.</returns> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static T TryResolve<T>(this IWindsorContainer container) { object result = TryResolve(container, typeof(T)); if (result != null) { return (T)result; } return default(T); } /// <summary> /// Utility method to try to resolve a service from the container avoiding an exception if the container cannot build the type. /// </summary> /// <param name="container">The cointainer that will be used to resolve the type.</param> /// <param name="typeToResolve">The type to resolve.</param> /// <returns>The instance of <paramref name="typeToResolve"/> built up by the container.</returns> [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] public static object TryResolve(this IWindsorContainer container, Type typeToResolve) { object resolved; try { resolved = Resolve(container, typeToResolve); } catch { resolved = null; } return resolved; } /// <summary> /// Resolves a service from the container. If the type does not exist on the container, /// first registers it with transient lifestyle. /// </summary> /// <param name="container"></param> /// <param name="type"></param> /// <returns></returns> public static object ResolveEx(this IWindsorContainer container, Type type) { if (type.IsClass && !container.Kernel.HasComponent(type)) { container.Kernel.AddComponent(type.FullName, type, LifestyleType.Transient); } return container.Resolve(type); } /// <summary> /// Resolves a service from the container. If the type does not exist on the container, /// first registers it with transient lifestyle. /// </summary> /// <param name="container"></param> /// <param name="type"></param> /// <returns></returns> private static object Resolve(this IWindsorContainer container, Type type) { return ResolveEx(container, type); } /// <summary> /// Registers the type on the container. /// </summary> /// <typeparam name="TServiceType">The type of the interface.</typeparam> /// <typeparam name="TClassType">The type of the service.</typeparam> /// <param name="container">The container.</param> public static void RegisterType<TServiceType, TClassType>(this IWindsorContainer container) { RegisterType<TServiceType, TClassType>(container, true); } /// <summary> /// Registers the type on the container. /// </summary> /// <typeparam name="TServiceType">The type of interface.</typeparam> /// <typeparam name="TClassType">The type of the service.</typeparam> /// <param name="container">The container.</param> /// <param name="singleton">if set to <c>true</c> type will be registered as singleton.</param> public static void RegisterType<TServiceType, TClassType>(this IWindsorContainer container, bool singleton) { if (!container.Kernel.HasComponent(typeof(TServiceType))) { container.Kernel.AddComponent(typeof(TClassType).FullName, typeof(TServiceType), typeof(TClassType), singleton ? LifestyleType.Singleton : LifestyleType.Transient); } } /// <summary> /// Registers the instance of a type on the container. /// </summary> /// <typeparam name="TServiceType">The type of interface.</typeparam> /// <param name="container">The container.</param> /// <param name="instance">The instance.</param> public static void RegisterInstance<TServiceType>(this IWindsorContainer container, TServiceType instance) { if (!container.Kernel.HasComponent(typeof(TServiceType))) { container.Kernel.AddComponentInstance(typeof(TServiceType).FullName, typeof(TServiceType), instance); } } } }
WindsorServiceLocator.cs
#region Usings! using System; using System.Collections.Generic; using Castle.Windsor; using Microsoft.Practices.ServiceLocation; #endregion namespace Core.Windsor { /// <summary> /// Adapts the behavior of the Windsor container to the common /// IServiceLocator /// </summary> public class WindsorServiceLocator : ServiceLocatorImplBase { private readonly IWindsorContainer container; /// <summary> /// Initializes a new instance of the <see cref="WindsorServiceLocator"/> class. /// </summary> /// <param name="container">The container.</param> public WindsorServiceLocator(IWindsorContainer container) { this.container = container; } /// <summary> /// When implemented by inheriting classes, this method will do the actual work of resolving /// the requested service instance. /// </summary> /// <param name="serviceType">Type of instance requested.</param> /// <param name="key">Name of registered service you want. May be null.</param> /// <returns> /// The requested service instance. /// </returns> protected override object DoGetInstance(Type serviceType, string key) { if (key != null) { return container.Resolve(key, serviceType); } return container.ResolveEx(serviceType); } /// <summary> /// When implemented by inheriting classes, this method will do the actual work of /// resolving all the requested service instances. /// </summary> /// <param name="serviceType">Type of service requested.</param> /// <returns> /// Sequence of service instance objects. /// </returns> protected override IEnumerable<object> DoGetAllInstances(Type serviceType) { return (object[]) container.ResolveAll(serviceType); } } }
Log4NetLogger.cs
#region Usings! using System.Reflection; using log4net; using Microsoft.Practices.Composite.Logging; #endregion namespace Core.Windsor { public class Log4NetLogger : ILoggerFacade { private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); #region ILoggerFacade Members public void Log(string message, Category category, Priority priority) { switch (category) { case Category.Debug: logger.Debug(message); break; case Category.Exception: logger.Error(message); break; case Category.Warn: logger.Warn(message); break; default: logger.Info(message); break; } } #endregion } }
