IoC in NuGet package

I lately came across an old piece of software which references several class libraries via NuGet and uses Castle.Windsor as a dependency container. The principle I will illustrate, however, applies to any IoC container.

I had to implement some additional features in one of the NuGet packages (MyNugetLib) referenced by the main application (MyProj). MyNuGetLib didn’t use IoC at all and wasn’t very nice. I didn’t like to just continue on the same pattern and I thought of implementing the new code using abstractions and services. This soon presented some challenges though.

1. Where do I register the dependencies?

Let’s consider, for example, that MyNugetLib has 2 classes: MyClass and MyService, which implement IMyClass and IMyService respectively. IMyService is injected in MyClass via constructor DI.

namespace MyNugetLib
{
    public interface IMyService
    {
        MyOtherType DoSomethingElse();
    }

    public MyService : IMyService 
    {
        MyOtherType DoSomethingElse() 
        {
            // do something important
        }
    }

    public interface IMyClass
    {
        MyType DoSomething();
    }

    public MyClass : IMyClass 
    {
        private IMyService myService;

        public MyClass(IMyService myInjectedService) 
        {
            this.myService = myInjectedService;
        }

        MyType DoSomething() 
        {
            // doing something important using this.myService
            this.myService.DoSomethingElse();
        }
    }
}

In MyProj (the consumer of the NuGet package) I would have:

namespace MyProj
{
    public class MyLocalClass : IMyLocalClass
    {
        private MyNugetLib.IMyClass myNugetLibService;
        
        public MyLocalClass(MyNugetLib.IMyClass service)
        {
            this.myNugetLibService = service;
        }
        
        internal MyLocalType DoSomethingHere() 
        {
            this.myNugetLibService.DoSomething();
            // etc
        }
    }
}

For this to work I would need to register in some IoC container that MyNugetLib.IMyClass is implemented by MyNugetLib.MyClass and MyNugetLib.IMyService is implemented by MyNugetLib.MyService. The question is where.

In my first attempt of resolving this problem I installed an IoC container in MyNugetLib (Castle.Windsor, the same one that is present in the main application MyProj). I then created an extension method in MyNugetLib for the WindsorContainer as follows:

namespace MyProj
{
    public static class WindsorContainerExtensions
    {
        public static void RegisterMyNugetLibServices(this IWindsorContainer container)
        {
            container.RegisterLocalServices();
        }
        
        private static void RegisterLocalServices(this IWindsorContainer container)
        {
            // register IMyClass and IMyService
            container.Register(...
        }
    }
}

Then in MyProj, I called the extension method from the main WindsorInstaller as follows:

using MyNugetLib;

namespace MyProj
{
    public class WindsorInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            // register local dependencies
            container.Register(...

            // register MyNugetLib dependencies
            container.RegisterMyNugetLibServices();
        }
    }

This worked. MyProj called the extension method on the IoC container. The implementation of the method was on MyNugetLib and the dependencies were registered correctly.

However, there was a drawback from this approach. By doing this I had introduced a direct dependency on Castle.Windsor in MyNugetLib. I had also introduced a constraint on any consumers of MyNugetLib to also use Castle.Windsor. I didn’t like this. I tried to explore other approaches but I couldn’t think of anything else I could do. I couldn’t find much online about this specific problem either, so I posted a question about this on StackOverflow (https://stackoverflow.com/questions/63266523/dependency-injection-in-nuget-package-library). I was very pleased with the reactions of the community and a couple of people were really helpful, particularly one who pointed me to a better solution (thanks again https://stackoverflow.com/users/14019150/insane-developer).

The better solution is registering the dependencies in the main application (MyProj), removing any IoC logic from MyNugetLib. This gives control to the main application on which services in MyNugetLib will be used. MyProj can even declare local services and inject them in MyNugetLib if needed, as long as they respect the interfaces. This removes the dependency on Castle.Windsor from MyNugetLib and makes it IoC-agnostic. Any IoC container can be used to register those dependencies in the consumer.

In MyNugetLib I created 2 marker interfaces IMyNugetLibSingleton and IMyNugetLibTransient, to determine the scope of these services once registered. Castle.Windsor has a nice feature which allows you to register all the services which implement a particular interface with one single “register” statement. I wanted MyClass to be scoped as Singleton for the second challenge which I will explain later.

namespace MyNugetLib.DI
{
    public interface IMyNugetLibSingleton {}

    public interface IMyNugetLibTransient {}
}

namespace MyNugetLib
{
    public interface IMyService : IMyNugetLibTransient 
    {
        MyOtherType DoSomethingElse();
    }

    public MyService : IMyService 
    {
        MyOtherType DoSomethingElse() 
        {
            // do something important
        }
    }

    public interface IMyClass : IMyNugetLibSingleton 
    {
        MyType DoSomething();
    }

    public MyClass : IMyClass 
    {
        private IMyService myService;

        public MyClass(IMyService myInjectedService) 
        {
            this.myService = myInjectedService;
        }

        MyType DoSomething() 
        {
            // doing something important using this.myService
            this.myService.DoSomethingElse();
        }
    }
}

Then, in MyProj I registered these dependencies as follows:

namespace MyProj
{
    public class WindsorInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
             container.Register(Classes.FromAssemblyContaining(typeof(MyNugetLib.DI.IMyNugetLibSingleton)).BasedOn<MyNugetLib.DI.IMyNugetLibSingleton>().WithService.FromInterface().LifestyleSingleton());

             container.Register(Classes.FromAssemblyContaining(typeof(MyNugetLib.DI.IMyNugetLibTransient)).BasedOn<MyNugetLib.DI.IMyNugetLibTransient>().WithService.FromInterface().LifestyleTransient());
            
            // register local services
            container.Register(...         
        }
    }
    
    public class MyLocalClass : IMyLocalClass
    {
        private MyNugetLib.IMyClass myNugetLibService;
        
        public MyLocalClass(MyNugetLib.IMyClass service)
        {
            this.myNugetLibService = service;
        }
        
        internal MyLocalType DoSomethingHere() 
        {
            this.myNugetLibService.Value.DoSomething();
            // etc
        }
    }
}

That was the final solution to the first challenge. In summary, it is better to register dependencies for a NuGet package in the package consumer rather than in the package itself.

2. How do I deal with static Lazy singletons?

One thing that was making the above solution complicated to implement was the way in which MyClass was being instantiated in MyNugetLib before my changes:

namespace MyNugetLib
{
    public MyClass
    {
        private static readonly Lazy<MyClass> LazyInstance = new Lazy<MyClass>(() => new MyClass());

        private MyClass() {}

        public static MyClass Instance => LazyInstance.Value;
    }

In MyProj it was being consumed as follows:

namespace MyProj
{
    public class MyLocalClass
    {
        public MyLocalClass()
        {
        }
        
        internal MyLocalType DoSomethingHere() 
        {
            MyNugetLib.MyClass.Instance.DoSomething();
        }
    }
}

The problem was I couldn’t inject any dependencies in MyNugetLib.MyClass because it was being instantiated as a private static Lazy<T>. This was the old way of using Singletons.

I had to do some more work to resolve this problem. I changed the implementation of the constructor in MyClass to use injected dependencies and registered those dependencies as Singletons in the IoC container (see solution to point 1). However, I also had to change all the references to MyNugetLib.MyClass.Instance in MyProj so that they would use an injected service instead of the static property. This took some rework, but it was worth it.

Another thing I learnt from this was that you can inject lazy services using constructor DI.

My final solution for consuming MyNugetLib.MyClass in MyProj was as follows:

namespace MyProj
{
    public class WindsorInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            // enable registration of lazy services
            container.Register(Component.For<ILazyComponentLoader>().ImplementedBy<LazyOfTComponentLoader>());

            container.Register(Classes.FromAssemblyContaining(typeof(MyNugetLib.DI.IMyNugetLibSingleton)).BasedOn<MyNugetLib.DI.IMyNugetLibSingleton>().WithService.FromInterface().LifestyleSingleton());

            container.Register(Classes.FromAssemblyContaining(typeof(MyNugetLib.DI.IMyNugetLibTransient)).BasedOn<MyNugetLib.DI.IMyNugetLibTransient>().WithService.FromInterface().LifestyleTransient());
            
            // register local services
            container.Register(...
        }
    }
    
    public class MyLocalClass : IMyLocalClass
    {
        private Lazy<MyNugetLib.IMyClass> myNugetLibLazyService
        
        public MyLocalClass(Lazy<MyNugetLib.IMyClass> lazyService)
        {
            this.myNugetLibService = lazyService;
        }
        
        internal MyLocalType DoSomethingHere() 
        {
            this.myNugetLibService.Value.DoSomething();
            // etc
        }
    }
}

In MyProj.MyLocalClass I defined a Lazy<T> service which was injected in the constructor. The benefit of doing this is that T is not resolved until you call T.Value. This is useful when a dependency isn’t used on every call to a class instance but only in certain scenarios (as was the case with MyClass in my context).

In summary: The Singleton scope can be defined in the IoC container. There is no need to use static properties and private constructos to define singleton classes. This would make DI impossible. You can also inject lazy services using constructor DI and these will be resolved only when needed to reduce overhead in cases when they’re not always needed.

I hope this is useful to somebody. Please feel free to comment and/or suggest any better solutions.

Leave a comment