Skip to content

Decouple IoC/DI container when authoring a C# library

Intro

Often when writing library code the integration of an IoC/DI container (eg. Unity, SimpleInjector, etc.) gets a topic. My goals are to use Inversion Of Control (IoC) and dependency injection (DI) to decouple things but don’t force the application to use one or another specific container. The user of my libraries should be free in choosing its IoC/DI container. On the other hand I like to give the users of my library a easy way to register the defaults types – again – without forcing one or another IoC/DI container product.

I noticed how ASP.Net core does their DI: they ship their own IoC/DI with the option to replace it with the one of your choice. For my little libraries this feels like an overkill. Even if writing a IoC/DI would be a nice and cool programming task 😉

Solution

So I decided to do it way simpler by introducing a interface for register types with any IoC/DI container. I normally don’t use the Service-Locator pattern in my source-codes but there are very rare situations where it gets hard without it. So I also added an minimalistic interface for service location.

IServiceContainer:

It features three methods for register types with the DI container: RegisterTransient(), RegisterSingleton() and RegisterInstance(). See code and doc below for more details.

    /// <summary>
    /// Specify the API of a basic Service Container implementation.
    /// </summary>
    /// <remarks>
    /// All RegisterType-Calls should be implemented against this interface so the
    /// final DI/IoC-Container can be freely choosen if there is a IServiceContainer
    /// implementation for it. If not one can easely write its own.
    /// </remarks>
    public interface IServiceContainer
    {
        /// <summary>
        /// Registers the type <typeparamref name="TFrom"/> to be implemented
        /// by type <typeparamref name="TTo"/> with an optional <paramref name="factory"/>.
        /// TRANSIENT: Each time the type is resolved it will create a new instance.
        /// </summary>
        /// <typeparam name="TFrom">The type of from.</typeparam>
        /// <typeparam name="TTo">The type of to.</typeparam>
        /// <param name="factory">The optional factory expression.</param>
        void RegisterTransient<TFrom, TTo>(Func<TFrom> factory = null) where TTo : TFrom;

        /// <summary>
        /// Registers the type <typeparamref name="TFrom"/> to be implemented
        /// by type <typeparamref name="TTo"/> with an optional <paramref name="factory"/>.
        /// Singleton: A new instance is created once and then used as a Singleton.
        /// </summary>
        /// <typeparam name="TFrom">The type of from.</typeparam>
        /// <typeparam name="TTo">The type of to.</typeparam>
        /// <param name="factory">The optional factory expression.</param>
        void RegisterSingleton<TFrom, TTo>(Func<TFrom> factory = null) where TTo : TFrom;

        /// <summary>
        /// Registers the type <typeparamref name="TFrom"/> to be implemented
        /// by type <typeparamref name="TTo"/> with a pre-instantiated <paramref name="instance"/>.
        /// Instance: The given <paramref name="instance"/> will be used. No new instance is created.
        /// </summary>
        /// <remarks>
        /// Some containers refer this as "external".
        /// </remarks>
        /// <typeparam name="TFrom">The type of from.</typeparam>
        /// <param name="instance">The pre-created instance to use when an instance of <typeparamref name="TFrom"/> is requested.</param>
        void RegisterInstance<TFrom>(object instance);
    }

IServiceLocator:

As mentioned I try not do use the service-locator pattern (why?) but rarely I have to. So I added the IServiceLocatorinterface with the methods Resolve()and ResolveAll().

    /// <summary>
    /// Interface for locating/resolving services/types.
    /// </summary>
    /// <remarks>Use this service locator pattern with care. Use constructor parameters instead
    /// whereever possible!</remarks>
    public interface IServiceLocator
    {
        /// <summary>
        /// Resolves a single instance of <typeparamref name="T"/>.
        /// </summary>
        /// <typeparam name="T">The Type one needs a instance of.</typeparam>
        /// <returns>A single instance of <typeparamref name="T"/>.</returns>
        T Resolve<T>();

        /// <summary>
        /// Resolves all instances ot <typeparamref name="T"/>.
        /// </summary>
        /// <typeparam name="T">The Type of the instances to resolve.</typeparam>
        /// <returns>All instances ot <typeparamref name="T"/>.</returns>
        IEnumerable<T> ResolveAll<T>();
    }

You now may ask “Who does the real work?”.

I have an assembly / NuGet package for the concrete implementation of the above interfaces using Microsoft Unity Version 4. Its basically an adapter that routes the interface to the corresponding Unity API:

    /// <summary>
    /// Implements <see cref="IServiceContainer"/> and <seealso cref="IServiceLocator"/> using Microsoft Unity. Use this
    /// class if you like to get the isolutions.Library default registreions to
    /// register with a Unity Container.
    /// </summary>
    /// <seealso cref="IServiceContainer" />
    /// <seealso cref="IUnityContainer"/>
    public class UnityServiceContainer : IServiceContainer, IServiceLocator
    {
        private readonly IUnityContainer container;

        /// <summary>
        /// Initializes a new instance of the <see cref="UnityServiceContainer" /> class.
        /// </summary>
        /// <param name="container">The optional Unity container.</param>
        public UnityServiceContainer(IUnityContainer container = null)
        {
            this.container = container ?? new UnityContainer();

            // Register Unity-Implementation
            RegisterSingleton<IServiceLocator, UnityServiceContainer>(() => this);
        }

        /// <inheritDoc/>
        public void RegisterTransient<TFrom, TTo>(Func<TFrom> factory = null) where TTo : TFrom
        {
            CheckContainer();

            if (factory == null)
            {
                container.RegisterType<TFrom, TTo>();
            }
            else
            {
                container.RegisterType<TFrom, TTo>(new InjectionFactory(c => factory()));
            }
        }

        /// <inheritDoc/>
        public void RegisterSingleton<TFrom, TTo>(Func<TFrom> factory = null) where TTo : TFrom
        {
            CheckContainer();

            if (factory == null)
            {
                container.RegisterType<TFrom, TTo>(new ContainerControlledLifetimeManager());
            }
            else
            {
                container.RegisterType<TFrom, TTo>(
                    new ContainerControlledLifetimeManager(),
                    new InjectionFactory(c => factory()));
            }
        }

        /// <inheritDoc/>
        public void RegisterInstance<TFrom>(object instance)
        {
            CheckContainer();

            container.RegisterInstance(typeof(TFrom), instance);
        }

        /// <inheritDoc/>
        public T Resolve<T>()
        {
            CheckContainer();

            return container.Resolve<T>();
        }

        /// <inheritDoc/>
        public IEnumerable<T> ResolveAll<T>()
        {
            CheckContainer();

            return container.ResolveAll<T>();
        }


        private void CheckContainer()
        {
            if (container == null)
            {
                throw new InvalidOperationException("UnityServiceContainer: No container instance configured!");
            }
        }
    }

Usages:

With these interfaces in place the core- as well as my other libraries can give the application service registration helpers like this one:

    public static class ServiceRegistration
    {
        public static void UseCore(this IServiceContainer container)
        {
            if (container == null) throw new ArgumentNullException(nameof(container));

            container.RegisterTransient<ISecurityHelper, SecurityHelper>();
            container.RegisterTransient<IUserService, UserService>();
            ...
        }
    }

The application can build up the container instance during startup with code like this very easily and request the libraries to add their default service registrations:

var result = new UnityServiceContainer();
result.UseCore();
result.UseCoreEF6<SampleContext>();
result.UseSampleCore();
...

Conclusion

My core library defines the dependency injection interfaces and all other libraries use only these two interfaces. This means they all are independent from what IoC/DI container is used by the application as long as they get an implementation of these two interfaces.

The Unity implementation is included in the app with a simple NuGet package the app can install. So the app can use my libraries and then decide which of my DI NuGet packages to install. One even can change the DI later on as needed. For example when moving from the full .Net framework to the .Net Core (more on this later).

It’s simple, small and elegant – AND: I didn’t implement my own IoC/DI 🙂

One thought on “Decouple IoC/DI container when authoring a C# library Leave a comment

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 )

Facebook photo

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

Connecting to %s

%d bloggers like this: