14
votes

How can I inject one class into another inside a .NET Core library project? Where should I configure DI as it is done in StartUp Class ConfigureServices in API project?

5
Have the class explicitly depend on the other (better yet, its abstraction) and then configure the container at the composition root. (Startup) - Nkosi
There is no Startup class in Class library. - Dazhush
As @Nkosi said, your library should not concern itself with dependency injection, just the inversion of control, i.e. externalizing its dependencies, so that they can be injected. The job of configuring the DI container is for the application utilizing the library(ies). If there's a lot of services to be configured, you can abstract this via adding an IServiceCollection extension in your library, but the app should be what actually calls that. - Chris Pratt

5 Answers

8
votes

There are many thought processes for how you manage this, as eventually, the caller will need to register your DI processes for you.

If you look at the methods used by Microsoft and others, you will typically have an extension method defined with a method such as "AddMyCustomLibrary" as an extension method off of the IServiceCollection. There is some discussion on this here.

16
votes

After googling a lot I could not find a comprehensive answer with an example to this question. Here is what should be done to use DI in Class library.

In your library:

public class TestService : ITestService
{
    private readonly ITestManager _testManager;

    public TestService(ITestManager testManager)
    {
        _testManager = testManager;
    }
}

public class TestManager : ITestManager 
{
    private readonly ITestManager _testManager;

    public TestManager()
    {
    }
}

Then extend IServiceCollection in the library:

public static class ServiceCollectionExtensions
{
    public static void AddTest(this IServiceCollection services)
    {
        services.AddScoped<ITestManager, TestManager>();
        services.AddScoped<ITestService, TestService>();
    }
}

Lastly in the main app StartUp (API, Console, etc):

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTest();
    }
3
votes

You can use Hosting Startup assemblies class library as an alternative to explicitly register them from the calling assembly.

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/platform-specific-configuration?view=aspnetcore-3.1#class-library

[assembly: HostingStartup(typeof(HostingStartupLibrary.ServiceKeyInjection))]
namespace HostingStartupLibrary
{
    public class Startup : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder)
        {
            builder.ConfigureServices((context, services) => {
                services.AddSingleton<ServiceA>();
            });
        }
    }
}
1
votes

Dependency Injection is configured at the Composition Root, basically the application entry point. If you do not have control over the application entry point you can not force anyone to use dependency injection with your class library. However you can use interface based programming and create helper classes to register every type in your library for a variety of Composition Root scenarios which will allow people to use IOC to instantiate your services regardless of whatever type of program they are creating.

What you can do is make services in your class library depend on interfaces of other services in your library so that the natural way to use them would be to register your services with the container that is in use and also allow for more efficient unit testing.

0
votes

I'm not sure I fully understood your intent... But maybe you can make your implementation spin its own private ServiceProvider, something like this:

using System.IO;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

public class MyBlackBox {
  private readonly IServiceProvider _services = BuildServices();

  protected MyBlackBox() {}

  public static MyBlackBox Create() {
    return _services.GetRequiredService<MyBlackBox>();
  }

  private static void ConfigureServices(IServiceCollection services) {
    services.AddTransient<MyBlackBox>();

    // insert your dependencies here
  }

  private static IServiceProvider BuildServices() {
    var serviceCollection = new ServiceCollection();
    serviceCollection.AddLogging();
    serviceCollection.AddOptions();

    serviceCollection.AddSingleton(config);
    serviceCollection.AddSingleton<IConfiguration>(config);

    ConfigureServices(serviceCollection);

    return serviceCollection.BuildServiceProvider();
  }

  private static IConfigurationRoot BuildConfig() {
    var path = Directory.GetCurrentDirectory();
    var builder = new ConfigurationBuilder().SetBasePath(path).AddJsonFile("appsettings.json");
    return builder.Build();
  }
}

You can then register your implementation on the "Parent" ServiceProvider, and your dependencies would not be registered on it.

The downside is that you'll have to reconfigure everything, mainly logging and configuration.

If you need access to some services from the parent ServiceProvider, you can create something to bind them together:

public static void BindParentProvider(IServiceProvider parent) {
  _services.AddSingleton<SomeService>(() => parent.GetRequiredService<SomeService>());
}

I'm pretty sure there's better ways to create nested ServiceProviders, though.