Dazhush
Dazhush

Reputation: 1781

Dependency Injection in .NET Core inside a class library

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?

Upvotes: 61

Views: 57580

Answers (6)

sergiokml
sergiokml

Reputation: 111

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();
    }
}

Upvotes: 0

Fábio Batista
Fábio Batista

Reputation: 25270

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.

Upvotes: 9

Aran Mulholland
Aran Mulholland

Reputation: 23935

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.

Upvotes: 6

Dazhush
Dazhush

Reputation: 1781

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();
    }

Upvotes: 71

Deepak Mishra
Deepak Mishra

Reputation: 3183

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>();
            });
        }
    }
}

Upvotes: 7

Mitchel Sellers
Mitchel Sellers

Reputation: 63126

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.

Upvotes: 20

Related Questions