49

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?

Fábio Batista
  • 25,002
  • 3
  • 56
  • 68
Dazhush
  • 1,575
  • 1
  • 12
  • 18
  • 1
    Have the class explicitly depend on the other (better yet, its abstraction) and then configure the container at the composition root. (Startup) – Nkosi Apr 27 '20 at 15:02
  • 8
    There is no Startup class in Class library. – Dazhush Apr 27 '20 at 15:09
  • 7
    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 Apr 27 '20 at 15:38

7 Answers7

63

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();
    }
Dazhush
  • 1,575
  • 1
  • 12
  • 18
  • 3
    Startup doesn't get called in a class library though. – JacobIRR May 06 '21 at 15:46
  • Startup is in API/Console project. It suppose to call the library – Dazhush May 07 '21 at 07:55
  • @Dazhush lets say my solution has a web api project and a console app, if my console app wants to consume this class library then the console app should pass in IConfiguration when using any classes from this class library with DI? – user1066231 Jun 14 '21 at 16:35
  • 1
    @user1066231 Yes. You can pass anything you want to the AddTest method (including IConfiguration). – Dazhush Jun 15 '21 at 19:57
  • 1
    You don't need to add the `ServiceCollectionExtensions` just use `services.AddScoped();` in your API/Console app inside `ConfigureServices` method and you are good to go. I tried it and it works seamless. – Kumar Apr 27 '22 at 04:49
14

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.

Mitchel Sellers
  • 62,228
  • 14
  • 110
  • 173
5

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.

Aran Mulholland
  • 23,555
  • 29
  • 141
  • 228
5

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.

Fábio Batista
  • 25,002
  • 3
  • 56
  • 68
4

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

https://learn.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>();
            });
        }
    }
}
Deepak Mishra
  • 2,984
  • 1
  • 26
  • 32
  • 2
    Singleton lifetime is whole the application, from startup to the end. using singleton is not the solution! – Ali Borjian Feb 03 '21 at 16:02
  • 3
    @AliBorjian Did I say that you use only singleton only? I have just given an example how to use dependency injection is class library separately. – Deepak Mishra Feb 03 '21 at 19:31
0

You can look at ServiceCollection Extension Pattern.

https://dotnetcoretutorials.com/2017/01/24/servicecollection-extension-pattern/

If you write this extension in class library, you can inject classes/services in this.

But I don't know is it a good pattern ?

Mehmet Topçu
  • 1
  • 1
  • 16
  • 31
0

so I can call the library with its services already attached, just use them.

this works for me:

public class LibraryBase
{
   ctor... (múltiple services)
   public static IHostBuilder CreateHostBuilder(IHostBuilder host)
   {
      return host.ConfigureServices(... services)
   }
}

Main:

public class Program
{
   Main{... ConfigureServicesAsync()}

    private static async Task ConfigureServicesAsync(string[] args)
    {
    IHostBuilder? host = new HostBuilder();
    host = Host.CreateDefaultBuilder(args);
    LibraryBase.CreateHostBuilder(host);

    host.ConfigureHostConfiguration()
    // ... start app
     await host.StartAsync();
    }
}
sergiokml
  • 41
  • 6