在.NET Core类库中实现依赖注入

53

在.NET Core库项目中,我如何将一个类注入到另一个类中? 在哪里配置DI,就像在API项目的StartUp Class ConfigureServices中一样?


1
将类明确依赖于另一个类(最好是其抽象类),然后在组合根处配置容器。(启动) - Nkosi
9
类库中没有“Startup”类。 - Dazhush
7
正如@Nkosi所说,你的库不应涉及依赖注入,仅涉及控制反转,即将其依赖项外部化,以便它们可以被注入。配置DI容器的工作是由使用该库的应用程序负责的。如果有很多服务需要配置,你可以通过在库中添加IServiceCollection扩展来抽象化这个过程,但实际调用它的应该是应用程序。 - Chris Pratt
7个回答

65

在进行了大量搜索之后,我没有找到一个带有示例的全面答案来回答这个问题。以下是在类库中使用 DI 的步骤。

在您的类库中:

public class TestService : ITestService
{
    private readonly ITestManager _testManager;

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

public class TestManager : ITestManager 
{
    private readonly ITestManager _testManager;

    public TestManager()
    {
    }
}

然后在库中扩展IServiceCollection:

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

最后,在主应用程序 StartUp(API、控制台等)中:

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

3
启动函数在类库中没有被调用。 - JacobIRR
启动项在 API/控制台项目中。它应该调用库。 - Dazhush
假设我的解决方案有一个Web API项目和一个控制台应用程序,如果我的控制台应用程序想要使用这个类库,则在使用该类库中的任何类时,控制台应用程序应该传递IConfiguration以进行依赖注入? - user1066231
1
@user1066231 是的。您可以将任何东西传递给AddTest方法(包括IConfiguration)。 - Dazhush
1
你不需要添加 ServiceCollectionExtensions,只需在 API/控制台应用程序的 ConfigureServices 方法中使用 services.AddScoped<ITestManager, TestManager>(); 即可。我尝试过了,它可以无缝运行。 - Kumar
我们应该把迁移文件放在类库还是API/控制台项目? - undefined

14

针对如何管理这一问题,有许多思考过程,因为最终,调用方需要为您注册DI(依赖注入)处理过程。

如果您观察微软和其他公司使用的方法,通常会定义一个扩展方法,并将其作为“AddMyCustomLibrary”这样的方法添加到IServiceCollection中。关于此问题有一些讨论请参见此处


7

我不确定我完全理解你的意图……但也许你可以让你的实现自己创建一个私有的 ServiceProvider,像这样:

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

您可以在“父”ServiceProvider上注册您的实现,这样您的依赖关系就不会在它上注册。

缺点是,您将不得不重新配置所有内容,主要是日志记录和配置。

如果您需要访问来自父ServiceProvider的某些服务,您可以创建绑定它们的东西:

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

我很确定有更好的方法创建嵌套的ServiceProvider,不过。


5

依赖注入是在组合根处配置的,基本上是应用程序的入口点。如果您无法控制应用程序的入口点,则无法强制任何人使用依赖注入来使用您的类库。但是,您可以使用基于接口的编程并创建帮助类来为您的类库中的每个类型注册各种组合根场景,这将允许人们使用IOC来实例化您的服务,而不管他们正在创建哪种类型的程序。

您可以使类库中的服务依赖于库中其他服务的接口,以便自然地使用它们将其注册到使用的容器中,并允许更有效的单元测试。


5
您可以使用Hosting Startup程序集类库作为替代方案,而不是从调用程序集中显式注册它们。

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

2
单例生命周期是整个应用程序,从启动到结束。使用单例并不是解决方案! - Ali Borjian
4
@AliBorjian,我可没说你只能用单例模式啊。我只是举了个例子,说明如何将依赖注入用在类库中。 - Deepak Mishra

0

这样我就可以调用已经附加了服务的库,直接使用它们。

这对我很有效:

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

主函数:

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

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接