This project is read-only.

SimpleInjector Prism Bootstrapper

May 18, 2013 at 11:55 AM
Hi all,

So a colleague directed me towards SimpleInjector after viewing the stats here. He felt it had the right level of features for his MVC project, whilst being among the fastest around.

I'm starting a new prism app (still feeling my way around the basics of prism/MVVM to be honest), but I was wondering if there is a SI bootstrapper around anywhere for Prism?

If not, can someone point me in the direction of where I can see how the bootstrappers are constructed (i.e. what I'd need to do to roll my own).

Thanks
Fergal
Coordinator
May 21, 2013 at 12:13 PM
We currently don't support Prism and as far as I know there is no NuGet package or Github repository provided by others that implement a Bootstrapper for Simple Injector.

After looking at the Prism.StructureMapExtensions NuGet package, I came up with the following code for Simple Injector:

This is the base class from which your own application specific bootstrapper must derive
using System;
using System.Diagnostics.CodeAnalysis;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

using CommonServiceLocator.SimpleInjectorAdapter;
    
using Microsoft.Practices.Prism;
using Microsoft.Practices.Prism.Events;
using Microsoft.Practices.Prism.Logging;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Prism.Regions.Behaviors;
using Microsoft.Practices.ServiceLocation;
    
using SimpleInjector;

public abstract class SimpleInjectorBootstrapper : Microsoft.Practices.Prism.Bootstrapper
{
    private bool useDefaultConfiguration = true;

    private readonly ILoggerFacade loggerFacade = new TraceLogger();

    public ILoggerFacade LoggerFacade { get { return loggerFacade; } }

    private Container Container { get; set; }

    public override void Run(bool runWithDefaultConfiguration)
    {
        this.useDefaultConfiguration = runWithDefaultConfiguration;

        this.Container = CreateContainer();

        ConfigureContainer();
        ConfigureRegionAdapterMappings();
        ConfigureDefaultRegionBehaviors();
        RegisterFrameworkExceptionTypes();

        var shell = CreateShell();
        if (shell != null)
        {
            RegionManager.SetRegionManager(shell, Container.GetInstance<IRegionManager>());
            RegionManager.UpdateRegions();
        }

        InitializeModules();
    }

    /// <summary>
    /// Configures the <see cref="IRegionBehaviorFactory"/>. This will be the list of default
    /// behaviors that will be added to a region. 
    /// </summary>
    protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
    {
        IServiceProvider provider = this.Container;

        var factory = (IRegionBehaviorFactory)provider.GetService(typeof(IRegionBehaviorFactory));

        if (factory != null)
        {
            factory.AddIfMissing(AutoPopulateRegionBehavior.BehaviorKey, typeof(AutoPopulateRegionBehavior));
            factory.AddIfMissing(BindRegionContextToDependencyObjectBehavior.BehaviorKey, typeof(BindRegionContextToDependencyObjectBehavior));
            factory.AddIfMissing(RegionActiveAwareBehavior.BehaviorKey, typeof(RegionActiveAwareBehavior));
            factory.AddIfMissing(SyncRegionContextWithHostBehavior.BehaviorKey, typeof(SyncRegionContextWithHostBehavior));
            factory.AddIfMissing(RegionManagerRegistrationBehavior.BehaviorKey, typeof(RegionManagerRegistrationBehavior));
        }

        return factory;
    }

    /// <summary>
    /// Registers in the <see cref="Container"/> the <see cref="Type"/> of the Exceptions
    /// that are not considered root exceptions by the <see cref="ExceptionExtensions"/>.
    /// </summary>
    protected override void RegisterFrameworkExceptionTypes()
    {
        ExceptionExtensions.RegisterFrameworkExceptionType(
            typeof(Microsoft.Practices.ServiceLocation.ActivationException));

        ExceptionExtensions.RegisterFrameworkExceptionType(
            typeof(SimpleInjector.ActivationException));
    }

    protected abstract void Configure(Container container);

    /// <summary>
    /// Configures the <see cref="IContainer"/>. May be overwritten in a derived class to add specific
    /// type mappings required by the application.
    /// </summary>
    protected virtual void ConfigureContainer()
    {
        this.Configure(this.Container);

        var adapter = new SimpleInjectorServiceLocatorAdapter(this.Container);

        this.Container.RegisterSingle<IServiceLocator>(adapter);

        this.Container.RegisterSingle<ILoggerFacade>(this.LoggerFacade);
        this.Container.RegisterSingle<IModuleCatalog>(GetModuleCatalog());

        if (!useDefaultConfiguration) return;

        this.Container.RegisterSingle<IModuleInitializer, ModuleInitializer>();
        this.Container.RegisterSingle<IModuleManager, ModuleManager>();
        this.Container.RegisterSingle<RegionAdapterMappings, RegionAdapterMappings>();
        this.Container.RegisterSingle<IRegionManager, RegionManager>();
        this.Container.RegisterSingle<IEventAggregator, EventAggregator>();
        this.Container.RegisterSingle<IRegionViewRegistry, RegionViewRegistry>();
        this.Container.RegisterSingle<IRegionBehaviorFactory, RegionBehaviorFactory>();
        this.Container.RegisterSingle<IRegionNavigationContentLoader, RegionNavigationContentLoader>();

        ServiceLocator.SetLocatorProvider(() => adapter);

        this.Verify();
    }

    // Override this method if verification is not possible or should be extended.
    protected virtual void Verify()
    {
        this.Container.Verify();
    }

    protected override void ConfigureServiceLocator()
    {
        // Service Locator configured in ConfigureContainer()
    }

    /// <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 override RegionAdapterMappings ConfigureRegionAdapterMappings()
    {
        var mappings = this.Container.GetInstance<RegionAdapterMappings>();

        mappings.RegisterMapping(typeof(Selector), this.Container.GetInstance<SelectorRegionAdapter>());
        mappings.RegisterMapping(typeof(ItemsControl), this.Container.GetInstance<ItemsControlRegionAdapter>());
        mappings.RegisterMapping(typeof(ContentControl), this.Container.GetInstance<ContentControlRegionAdapter>());

        return mappings;
    }

    /// <summary>
    /// Creates the <see cref="IContainer"/> that will be used as the default container.
    /// </summary>
    /// <returns>A new instance of <see cref="IContainer"/>.</returns>
    protected virtual Container CreateContainer()
    {
        return new Container();
    }

    /// <summary>
    /// Returns the module enumerator 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 new ModuleCatalog();
    }
}
You can derive your own application specific bootstrapper from this class:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;

using Microsoft.Practices.Prism.Modularity;

using SimpleInjector;
    
public class ApplicationBootstrapper : SimpleInjectorBootstrapper
{
    private IEnumerable<Type> moduleTypes;

    protected override void Configure(Container container)
    {
        this.moduleTypes = GetModuleTypesFromAssembliesInCurrentDomainBaseDirectory();

        container.RegisterAll<IModule>(this.moduleTypes);

        // TODO: Your registrations here.
    }

    protected override DependencyObject CreateShell()
    {
        // TODO: Your code here
        return new Shell();
    }
        
    protected override IModuleCatalog GetModuleCatalog()
    {
        var catalog = new ModuleCatalog();

        foreach (Type moduleType in this.moduleTypes)
        {
            catalog.AddModule(moduleType);
        }

        return catalog;
    }

    public static Type[] GetModuleTypesFromAssembliesInCurrentDomainBaseDirectory()
    {
        return (
            from assembly in LoadAssembliesFromCurrentDomainBaseDirectory()
            from type in assembly.GetExportedTypes()
            where !type.IsGenericType && !type.IsAbstract
            where typeof(IModule).IsAssignableFrom(type)
            select type)
            .ToArray();
    }

    private static IEnumerable<Assembly> LoadAssembliesFromCurrentDomainBaseDirectory()
    {
        return (
            from fileName in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory)
            where fileName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
            let assembly = LoadAssemblyFromPathOrNull(fileName)
            where assembly != null
            select assembly)
            .ToArray();
    }

    private static Assembly LoadAssemblyFromPathOrNull(string assemblyPath)
    {
        try { return Assembly.LoadFrom(assemblyPath); }
        catch { return null; }
    }
}
And I believe you need to run the bootstrapper from within the App class constructor:
        public App()
        {
            var bootStrapper = new ApplicationBootstrapper();
            bootStrapper.Run();
        }
Please note that I myself have no experience what so ever with Prism and have no idea what the quality is of the StructureMap bootstrapper. I just converted it to what I believe is the Simple Injector equivalent and ran it in a small test project to see if the configuration would succeed. That however doesn't mean it is correct. You will have to check this yourself, but please let me know how it goes. This way this thread can help others when integrating Prism with Simple Injector.
Marked as answer by dot_NET_Junkie on 2/26/2014 at 1:34 PM
May 21, 2013 at 1:12 PM
Thanks! I have to admit I'm fairly new to the whole stack (WPF, MVVM, Prism, IoC, etc). Done a fair amount of .Net programming, just never with those technologies.

I'll fire in the code you supplied when I get a chance, and see how it pans out, then let you know.

Thanks
Fergal
Coordinator
May 22, 2013 at 3:08 PM
Edited Jul 3, 2013 at 11:17 AM
You might also need to create a specially crafted IServiceLocator implementation to be able to resolve views from the container, as you can see in this thread.
May 22, 2013 at 9:18 PM
So I just gave it a really quick try, and found a couple of issues relating, I think, to missing references. I don't have time to look at it in any detail at the moment unfortunately, but I'll come back to this soon.

My only real thought so far, is that there is a fair amount of code in the ApplicationBootstrapper class. the Unity bootstrapper seems to require a lot less code to be up and running. I guess it moves a load of the code into the bootstrapper class.

I have a copy of the Unity code installed locally, so I may take a look at that and see what I can come up with between it and the code you provided. I suppose, ultimately if we can get it working, it might be worth creating as a Nuget package (or at least providing the solution here).

One general question though - what is the CommonServiceLocator.SimpleInjectorAdapter for?

Thanks
Fergal
Coordinator
May 22, 2013 at 9:33 PM
Don't forget that PRISM was built initially with Unity as its DI container. The Unity adapter for PRISM will probably be rather straightforward.

It would be great if this thread could result in the creation of a NuGet package.
what is the CommonServiceLocator.SimpleInjectorAdapter for?
PRISM depends on the Common Service Locator (CSL). CSL is an abstraction over DI containers. This allows us to swap DI containers, while PRISM can still request instances from the container. The CommonServiceLocator.SimpleInjectorAdapter is the CSL implementation for Simple Injector.
May 22, 2013 at 11:03 PM
Fair point.

I'll probably try to get your code working first, then see if we can clean up the user implementation somewhat in order to keep things simpler for users of the class. I'll let you know how I get on.

Thanks for the clarification of the CSL.