Agnostic IoC Pt 1- Building Glass Mapper for Autofac, StructureMap, Windsor By Changing 1 Nuget Package

As a few of might already know, we started a project a short while back to overcome the difficulties developers face when building reusable modules for systems. This project was centered on the need to continually write our inverted dependency calls manually to avoid ties to an IoC container such as Autofac / Windsor. Recently a client of mine and also a friend have independently approached me about this subject. The former wants to commission me and Simon from the team here, to help them out on a build for a client of theirs, this client has asked that we use Autofac as the dependency container and Glass Mapper as the ORM. The second wants to be able to use Glass Mapper with their current implementation, they however use structure map.

It would have been a reasonable enough task to duplicate and modify the Windsor implementation from github twice to allow this to happen, but I fancied a challenge and thought this would be a good test of the Agnostic IoC extensions that I have written and talked about Here. After all, it is the near perfect use case for such an implementation – 1 framework with a choice of end IoC containers depending on the client.

What is Agnostic IoC?
Agnostic IoC is a set of extensions that aims to abstract the IoC container away from the final consuming implementation, you write your code against Agnostic IoC and it adapts the calls to the real IoC. You can initialise it from self discovery on adapters or simply push in your own already built container. It in many cases will not replace your own IoC implementation, it’s primary focus is to allow you to ship your reusable modules so others can enjoy them without tying them to particular versions of IoC containers. If the IoC container of your choice isn’t supported, it is quick and easy to write an additional one simply by inheriting from the abstract ContainerManager class. When consuming the application, you simply need to install the nuget package that matches your chosen IoC. If you need to get at any of the original functionality – the ContainerAdapter is a generic in its own right, you can simply cast something like myContainerManager.Container as IWindsorContainer to get back to the original container (that said – you can push your own in so not sure how much use it will be 😉 )

The Test
The aim of the exercise was to give a container agnostic implementation of Glass Mapper, that was not aware of the container until it is actually consumed. In order to achieve this, it will require writing all of the registrations for Agnostic and simply plugging them through an adapter class when it is implemented.

Component Registration
If you are familiar with Glass Mapper, you will know that Michael and the team kindly give us a container based implementation utilising Castle Windsor. I started off by duplicating the functionality from the Windsor implementation on GitHub. Below is the original code for one of the windsor installers (for those who don’t know, these are simply grouped installers).

    public class ConfigurationResolverTaskInstaller : IWindsorInstaller
    {

        /// <summary>
        /// Gets the config.
        /// </summary>
        /// <value>
        /// The config.
        /// </value>
         public Config Config { get; private set; }

         /// <summary>
         /// Initializes a new instance of the <see cref="ConfigurationResolverTaskInstaller"/> class.
         /// </summary>
         /// <param name="config">The config.</param>
        public ConfigurationResolverTaskInstaller(Config config)
        {
            Config = config;
        }


        /// <summary>
        /// Performs the installation in the <see cref="T:Castle.Windsor.IWindsorContainer" />.
        /// </summary>
        /// <param name="container">The container.</param>
        /// <param name="store">The configuration store.</param>
        public virtual void Install(IWindsorContainer container, IConfigurationStore store)
        {
            // These tasks are run when Glass.Mapper tries to find the configuration the user has requested based on the type passed, e.g. 
            // if your code contained
            //       service.GetItem<MyClass>(id) 
            // the standard resolver will return the MyClass configuration. 
            // Tasks are called in the order they are specified below.

            container.Register(
              Component.For<IConfigurationResolverTask>()
                       .ImplementedBy<SitecoreItemResolverTask>()
                       .LifestyleCustom<NoTrackLifestyleManager>()
              );
            container.Register(
              Component.For<IConfigurationResolverTask>()
                       .ImplementedBy<MultiInterfaceResolverTask>()
                       .LifestyleCustom<NoTrackLifestyleManager>()
              );
            container.Register(
               Component.For<IConfigurationResolverTask>()
                        .ImplementedBy<TemplateInferredTypeTask>()
                        .LifestyleCustom<NoTrackLifestyleManager>()
               );

            container.Register(
                Component.For<IConfigurationResolverTask>()
                         .ImplementedBy<ConfigurationStandardResolverTask>()
                         .LifestyleCustom<NoTrackLifestyleManager>()
                );

            container.Register(
                Component.For<IConfigurationResolverTask>()
                         .ImplementedBy<ConfigurationOnDemandResolverTask<SitecoreTypeConfiguration>>()
                         .LifestyleCustom<NoTrackLifestyleManager>()
                );     
        }
    }

When converted to agnostic, this became:

    /// <summary>
    /// Configuration Resolver Tasks - These tasks are run when Glass.Mapper tries to find the configuration the user has requested based on the type passsed.
    /// </summary>
    public class ConfigurationResolverTaskInstaller : IContainerManagerGroupRegistration
    {
        public void RegisterComponents(IContainerAdapter containerAdapter)
        {
            containerAdapter.Register<IConfigurationResolverTask, SitecoreItemResolverTask>(LifetimeScope.Unowned);
            containerAdapter.Register<IConfigurationResolverTask, MultiInterfaceResolverTask>(LifetimeScope.Unowned);
            containerAdapter.Register<IConfigurationResolverTask, ConfigurationStandardResolverTask>(LifetimeScope.Unowned);
            containerAdapter.Register<IConfigurationResolverTask, ConfigurationOnDemandResolverTask<SitecoreTypeConfiguration>>(LifetimeScope.Unowned);
        }
    }

I obviously made sure that Windsor is not present in any of the modules when doing this.

Next, I turned my attention to the installer that is given for the Windsor implementation, this was also a windsor specific ‘installer’:

    public class SitecoreInstaller : IWindsorInstaller
    {
        /// <summary>
        /// Gets the config.
        /// </summary>
        /// <value>
        /// The config.
        /// </value>
        public Config Config { get; private set; }

        /// <summary>
        /// Gets or sets the data mapper installer.
        /// </summary>
        /// <value>
        /// The data mapper installer.
        /// </value>
        public IWindsorInstaller DataMapperInstaller { get; set; }

        /// <summary>
        /// Gets or sets the query parameter installer.
        /// </summary>
        /// <value>
        /// The query parameter installer.
        /// </value>
        public IWindsorInstaller QueryParameterInstaller { get; set; }

        /// <summary>
        /// Gets or sets the data mapper task installer.
        /// </summary>
        /// <value>
        /// The data mapper task installer.
        /// </value>
        public IWindsorInstaller DataMapperTaskInstaller { get; set; }

        /// <summary>
        /// Gets or sets the configuration resolver task installer.
        /// </summary>
        /// <value>
        /// The configuration resolver task installer.
        /// </value>
        public IWindsorInstaller ConfigurationResolverTaskInstaller { get; set; }

        /// <summary>
        /// Gets or sets the objection construction task installer.
        /// </summary>
        /// <value>
        /// The objection construction task installer.
        /// </value>
        public IWindsorInstaller ObjectionConstructionTaskInstaller { get; set; }

        /// <summary>
        /// Gets or sets the object saving task installer.
        /// </summary>
        /// <value>
        /// The object saving task installer.
        /// </value>
        public IWindsorInstaller ObjectSavingTaskInstaller { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="SitecoreInstaller"/> class.
        /// </summary>
        public SitecoreInstaller():this(new Config())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SitecoreInstaller"/> class.
        /// </summary>
        /// <param name="config">The config.</param>
        public SitecoreInstaller(Config config)
        {
            Config = config;



            DataMapperInstaller = new DataMapperInstaller(config);
            QueryParameterInstaller = new QueryParameterInstaller(config);
            DataMapperTaskInstaller = new DataMapperTaskInstaller(config);
            ConfigurationResolverTaskInstaller = new ConfigurationResolverTaskInstaller(config);
            ObjectionConstructionTaskInstaller = new ObjectionConstructionTaskInstaller(config);
            ObjectSavingTaskInstaller = new ObjectSavingTaskInstaller(config);
        }


        /// <summary>
        /// Performs the installation in the <see cref="T:Castle.Windsor.IWindsorContainer" />.
        /// </summary>
        /// <param name="container">The container.</param>
        /// <param name="store">The configuration store.</param>
        public virtual void Install(IWindsorContainer container, IConfigurationStore store)
        {
            // For more on component registration read: http://docs.castleproject.org/Windsor.Registering-components-one-by-one.ashx
            container.Install(
                DataMapperInstaller,
                QueryParameterInstaller,
                DataMapperTaskInstaller,
                ConfigurationResolverTaskInstaller,
                ObjectionConstructionTaskInstaller,
                ObjectSavingTaskInstaller
                );

            container.Register(
                Component.For<Glass.Mapper.Sc.Config>().Instance(Config)
                );
        }
    }

Which became this:

    public class SitecoreInstaller : IContainerManagerGroupRegistration
    {
        /// <summary>
        /// Gets the config.
        /// </summary>
        /// <value>
        /// The config.
        /// </value>
        public Config Config { get; private set; }

        /// <summary>
        /// Gets or sets the data mapper installer.
        /// </summary>
        /// <value>
        /// The data mapper installer.
        /// </value>
        public IContainerManagerGroupRegistration DataMapperInstaller { get; set; }

        /// <summary>
        /// Gets or sets the query parameter installer.
        /// </summary>
        /// <value>
        /// The query parameter installer.
        /// </value>
        public IContainerManagerGroupRegistration QueryParameterInstaller { get; set; }

        /// <summary>
        /// Gets or sets the data mapper task installer.
        /// </summary>
        /// <value>
        /// The data mapper task installer.
        /// </value>
        public IContainerManagerGroupRegistration DataMapperTaskInstaller { get; set; }

        /// <summary>
        /// Gets or sets the configuration resolver task installer.
        /// </summary>
        /// <value>
        /// The configuration resolver task installer.
        /// </value>
        public IContainerManagerGroupRegistration ConfigurationResolverTaskInstaller { get; set; }

        /// <summary>
        /// Gets or sets the objection construction task installer.
        /// </summary>
        /// <value>
        /// The objection construction task installer.
        /// </value>
        public IContainerManagerGroupRegistration ObjectionConstructionTaskInstaller { get; set; }

        /// <summary>
        /// Gets or sets the object saving task installer.
        /// </summary>
        /// <value>
        /// The object saving task installer.
        /// </value>
        public IContainerManagerGroupRegistration ObjectSavingTaskInstaller { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="SitecoreInstaller"/> class.
        /// </summary>
        public SitecoreInstaller():this(new Config())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SitecoreInstaller"/> class.
        /// </summary>
        /// <param name="config">The config.</param>
        public SitecoreInstaller(Config config)
        {
            Config = config;

            DataMapperInstaller = new DataMapperInstaller();
            QueryParameterInstaller = new QueryParameterInstaller();
            DataMapperTaskInstaller = new DataMapperTaskInstaller();
            ConfigurationResolverTaskInstaller = new ConfigurationResolverTaskInstaller();
            ObjectionConstructionTaskInstaller = new ObjectionConstructionTaskInstaller();
            ObjectSavingTaskInstaller = new ObjectSavingTaskInstaller();
        }

        public void RegisterComponents(IContainerAdapter containerAdapter)
        {
            containerAdapter.Register(DataMapperInstaller);
            containerAdapter.Register(QueryParameterInstaller);
            containerAdapter.Register(DataMapperTaskInstaller);
            containerAdapter.Register(ConfigurationResolverTaskInstaller);
            containerAdapter.Register(ObjectionConstructionTaskInstaller);
            containerAdapter.Register(ObjectSavingTaskInstaller);

            containerAdapter.Register<Config, Config>(Config);
        }
    }

Note that so far, the code is near identical.

Testing with different IoC containers
To ensure the implementation works, I created myself 3 unit test projects, one common one that was not aware of any IoC container, this simply allows me to write the majority of code as a common test. One with Windsor installed (still using the Agnostic IoC version and the final one with Autofac and here is the clever bit. The only difference between the project set up is simply the addition of the “Agnostic IoC – “

So – here we have the 2 different implementations of my tests (and these are the only file in the 2 projects !! )

        [Test]
        public void CanMap()
        {
            string containerKey = Guid.NewGuid().ToString(); // randomly generated to ensure the containers are not cross contaminated
            IContainerManager autofacContainerManager = new ContainerManager(new AutofacContainerAdapter(containerKey));
            CanMapBasicType(autofacContainerManager); // test in the non-IoC aware common class
        }
        [Test]
        public void CanMap()
        {
            IWindsorContainer container = new WindsorContainer();
            string containerKey = Guid.NewGuid().ToString();
            IContainerManager containerManager = new ContainerManager(new WindsorContainerAdapter(containerKey, container));
            CanMapBasicType(containerManager);
        }

Where can I get this amazing functionality?
This project is up on GitHub right now !! – Here

The nuget packages are also available off my nightly builds here: Cardinal Nightly Nuget Feed

Bear in mind, this is still in a beta phase, but I welcome people to play and contribute, maybe one day it will drive much bigger implementations.

Conclusion
As you can see, there is very little difference in use between Agnostic IoC and a regular IoC container, it means we can write our code just as we always have.

The demo above is probably going to be available somewhere once I have finalised it, however, it is a great illustration of a use case for Agnostic IoC and has validated why I wrote it in the first place.

Acknowledgments
As always – its great to say thanks – so thank you very much Michael, Tom and anyone else involved with Glass for the support and keep up the great work!

Advertisements

One thought on “Agnostic IoC Pt 1- Building Glass Mapper for Autofac, StructureMap, Windsor By Changing 1 Nuget Package

  1. Pingback: Glass Mapper – Introducing Glass Maps | CardinalCore

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s