简体   繁体   中英

Dependency Injection in NuGet package library

This builds on concepts discussed in this question How to manage Castle Windsor dependencies in a nuget library .

I am working on a project which uses Castle.Windsor for DI and references several custom NuGet packages. I have installed Castle.Windsor in one of the class libraries (let's call it MyNugetLib) referenced via NuGet and I've defined an extension method on IWindsorContainer in this library which registers the local services.

public static class WindsorContainerExtensions
{
    public static void RegisterMyNugetLibServices(this IWindsorContainer container)
    {
        container.RegisterLocalServices();
    }
    
    private static void RegisterLocalServices(this IWindsorContainer container)
    {
        container.Register(...)
    }
}

The project that references this NuGet package (let's call it MyProj) uses Windsor as well and in the installer it calls the extension method defined in the NuGet package. This all works. My question is how do I pass the Windsor container reference to MyNugetLib from MyProj? I have tried injecting IWindsorContainer in the constructor of a class where I need it, but this class uses Lazy instantiation as follows:

private static readonly Lazy<MyClass> LazyInstance = new Lazy<MyClass>(() => new MyClass());
    
private MyClass() {}

public static MyClass Instance => LazyInstance.Value;

From MyProj this is called as follows:

Lib.MyClass.Instance

The only way in which I've managed to make this work is exposing a public property in MyClass which is used in MyProj to set the WindsorContainer

public IWindsorContainer DIContainer { get; set; }

in MyProj

Lib.MyClass.DIContainer = MyProj.WindsorContainer

I don't particularly like this. Is there a better way of doing this?


Update: (thanks to insane_developer's suggestions)

The question really is: how do you inject dependencies in a Lazy constructor? If I could do this, then I could remove the dependency on Windsor from MyNugetLib.

So I would have

private IService1 service1;
private IService2 service2;

private MyClass(IService1 myService1, IService2 myService2) 
{
    this.service1 = myService1;
    this.service2 = myService2
}

public static MyClass Instance => LazyInstance.Value;

private static readonly Lazy<MyClass> LazyInstance = new Lazy<MyClass>(() => new MyClass(???));

How do I write the func above so it injects dependencies in my constructor?

I never used Castle.Windsor but I assume it would be like other containers. You don't really need to pass a reference to the container to your library. You should be able to configure all your mappings in the main application, which has a reference to your library. If you do what you suggest, you will have a concrete dependency on Castle.Windsor, which is not a good idea.

Thanks to @insane_developer's suggestions I was able to do this in a much cleaner way. The problem was that MyNugetLib was using static singletons created using Lazy<T> . Dependencies couldn't be injected in those constructors, therefore I was trying to figure out a good way to pass a reference to the DI container. However this may be done, it introduces a direct dependency on the DI package within MyNugetLib, which isn't great. The better solution is to use dependency injection consistently within MyNugetLib, refactor the classes with static singletons to have normal constructors with injected dependencies and register those dependencies from the main application (MyProj). In order to do this I had to refactor the way in which MyProj uses some of MyNugetLib classes so that it doesn't refer to singletons via static properties anymore, but via injected dependencies which are registered within the main DI container as singletons and injected as lazy dependencies. This is what I did (conceptually).

namespace MyNugetLib.DI
{
    public interface IMyNugetLibSingleton {}

    public interface IMyNugetLibTransient {}
}

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

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

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

    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

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
        }
    }
    
    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
        }
    }
}

Using IMyNugetLibSingleton and IMyNugetLibTransient I'm able to register all the classes which implement these with one single register statement. I have now moved the "lazyness" from MyNugetLib to MyProj. The injected dependency myNugetLibLazyService is resolved only when needed. MyNugetLib's dependencies can now be resolved using any DI contanier, at the choice of the consumer.

I'm pleased with this solution, but still open to suggestions if someone has any better ideas.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM