c#:DI 和附加参数

问题描述 投票:0回答:4

例如,我有一个具有以下依赖项的类

CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService

    protected readonly IDeviceService _deviceService;
    protected readonly IAzureFunctionLogService _azureFunctionLogService;
    protected readonly IDeviceValidationService _deviceValidationService;

所以,我可以为班级创建ctor:

    public CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService(
        IDeviceService deviceService,
        IDeviceValidationService deviceValidationService,
        IAzureFunctionLogService azureFunctionLogService)
    {
        _deviceService = deviceService;
        _deviceValidationService = deviceValidationService;
        _azureFunctionLogService = azureFunctionLogService;
    }

然后注入所有依赖项,例如:

services.AddTransient<CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService>();
               services.AddSingleton<Func<CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService>>(sp =>
                   () => sp.GetRequiredService<CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService>()
               );

然后像这样使用它:

    private readonly Func<CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService> _service;


        public FunctionDebugPendingStatusWorkflow(
Func<CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService> service,
            //....
            ILoggerFactory loggerFactory)
        {
            _service = service;
            //....
            _logger = loggerFactory.CreateLogger<FunctionDebugPendingStatusWorkflow>();
        }

所以,它工作正常。

但是我怎样才能给调用者中设置的ctor再添加一个参数呢?例如,我想将

deviceId
传递给 ctor 并且不能在
Program.cs
中使用依赖注入器将其描述为依赖项(在我的例子中)

我必须像这样创建“Init”方法:

    public void Init(int deviceId)
    {
        _device = _deviceService.GetDeviceById(deviceId);
        // ...
    }

并在那里添加逻辑。

然后我必须在使用 _service 方法之前调用

_service.Init(...);
。它有效,但所有缺点和潜在问题都是显而易见的(如果忘记打电话等)

如何使用DI传递这个参数?

c# dependency-injection parameters constructor parameter-passing
4个回答
3
投票

你可以这样做:

serviceCollection
    .AddScoped<IYourService>(s => new YourService(
         s.GetService<YourPreviouslyInjectedService1>(),
         s.GetService<YourPreviouslyInjectedService2>(),
         s.GetService<YourPreviouslyInjectedService3>(),
         deviceId
    )
);

但我建议不要这样做,并使用

IOptions
和注入的配置对象,如:

.Configure<YourConfigurationWithDeviceIdProperty>(
    c => builder.Configuration.GetSection("YourConfigSectionInAppSettings").Bind(c)
)

首先,您要避免 DI 层中的所有这些噪音,其次,您要确保注入正确的设置,因为您的构造函数将接受特定类型的参数,而不是可以是任何类型的原语(“123” ,“香蕉”等)。


1
投票

执行此操作的典型方法是不注入任何原语,而是注入唯一的配置类 (

SomethingSomethingSettings
) 以避免参数类型冲突,或者注入
IOptions<SomethingSomethingSettings>
以利用 .NET 配置支持.

另一种方法是注入一个可以在运行时使用的工厂类,它具有执行您需要的所有逻辑。因此,您将拥有一个

Init
成员,而不是
_device
方法和
_factory
成员,其
Create
方法将根据需要调用,并且具有它需要的上下文(同样,通常来自
SomethingSomethingSettings
类.


0
投票

如果您的

deviceId
不是来自应用程序设置,而是运行时的动态值,那么
Autofac
可以帮助您。

using Autofac;
using Autofac.Core;

public class Service1
{
    public object GetById(int someId) { }
}

public class Service2 { }

public interface IMainService { }

public interface IMainServiceFactory
{
    IMainService Create(int someId);
}

public class MainService : IMainService
{
    private readonly object something;

    public MainService(Service1 service1, Service2 service2, int someId)
    {
        something = service1.GetById(someId);
    }
}

public class MainServiceFactory : IMainServiceFactory
{
    private readonly ILifetimeScope lifetimeScope;

    public MainServiceFactory(ILifetimeScope lifetimeScope)
    {
        this.lifetimeScope = lifetimeScope.BeginLifetimeScope();
    }

    public IMainService Create(int someId)
    {
        return lifetimeScope.Resolve<IMainService>(
            new ResolvedParameter(
                // Predicate for the .ctor parameter: int someId
                (parameter, context) =>
                    parameter.ParameterType == typeof(int)
                    &&
                    parameter.Name == nameof(someId)

                // The resolved value for the .ctor parameter.
                (parameter, context) => someId
            )
        );
    }
}

要将

Autofac
与内置的 MS DI 集成,您可以使用 Autofac.Extensions.DependencyInjection nuget 包。

在 .NET 6 中,您可以执行以下操作:

using Autofac;
using Autofac.Extensions.DependencyInjection;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder
    .Host
    .UseServiceProviderFactory(new AutofacServiceProviderFactory());

builder
    .Services
    .AddScoped<Service1>()
    .AddScoped<Service2>()
    .AddScoped<IMainService, MainService>()
    .AddScoped<IMainServiceFactory, MainServiceFactory>();

// Inject the IMainServiceFactory where you need the resolve an instance of IMainService
IMainService mainService = mainServiceFactory.Create(42);


0
投票

✅ 您可以像这样使用 .NET 6 依赖注入来做到这一点。

首先,创建一个 .NET 6 控制台应用程序(带有顶级语句)并添加必要的 NuGet 包。

📦 安装 NuGet 包:

  • Microsoft.Extensions.DependencyInjection.Abstractions, 6.0.0
  • Microsoft.Extensions.Hosting, 6.0.0

  1. 添加用途:

    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.DependencyInjection;
    
  2. 创建一个可以注入到我们的服务和其他地方的依赖:

    public class Dependency : IDependency
    {
        // Initialize Id with a random value to validate that it's the same instance for the same scope
        public Guid Id { get; init; } = Guid.NewGuid();
    }
    
    // Interface for DI
    public interface IDependency
    {
        public Guid Id { get; }
    }
    
  3. 使用每个范围的自定义 ID 定义我们的服务:

    public class RequestService : IRequestService
    {
        public IDependency Dependency { get; init; }
    
        private int _id;
    
        // Constructor is used for standard dependency injection
        public RequestService(IDependency dependency)
        {
            Dependency = dependency;
        }
    
        // Init method is used to set custom ID via DI.
        public void Init(int id)
        {
            _id = id;
        }
    }
    
    public interface IRequestService
    {
        IDependency Dependency { get; }
    
        void Init(int id);
    }
    
  4. 创建主机生成器:

    var builder = Host.CreateDefaultBuilder();
    // You can do exactly the same with an `WebApplicationBuilder`
    // WebApplication.CreateBuilder(args);
    
  5. 开始配置DI:

    builder.ConfigureServices(services =>
    {
        // Must be scoped
        services.AddScoped<IDependency, Dependency>();
        // Must be scoped
        services.AddScoped<IRequestService, RequestService>();
    
        // <Add a factory method DI configuration here>
    });
    

    5.1。配置工厂以通过 DI 实例化您的服务,并在此期间设置任意 ID。

    💥 这是DI魔法的主要部分

    工厂功能将做:

    • int
      id作为输入。
    • 通过 DI 实例化
      IRequestService
      (本质上,将创建一个
      RequestService
      对象)。
    • 调用
      .Init(id)
      并将 ID 值作为参数传递。
    // Add into `builder.ConfigureServices(services => {})`
    
    // Must be scoped
    services.AddScoped<Func<int, IRequestService>>(implementationFactory: provider =>
    {
        // Create a new factory
        return new Func<int, IRequestService>(id =>
        {
            var result = provider.GetRequiredService<IRequestService>();
            result.Init(id);
            return result;
        });
    });
    
  6. 之后只需构建主机并创建一个 DI 范围:

    var host = builder.Build();
    
    var scope = host.Services.CreateScope();
    
  7. 最后,使用DI:

    // Resolve an `IDependency` just for the sake of the example
    var dependency = scope.ServiceProvider.GetRequiredService<IDependency>();
    
    // Resolve a factory for your service
    var requestServiceFactory = scope.ServiceProvider.GetRequiredService<Func<int, IRequestService>>();
    // Use a factory with custom ID
    var requestService = requestServiceFactory(32);
    

    注意:

    dependency
    requestService.Dependency
    将是同一个实例。


因此,如果您在任何地方注入

Func<int, IRequestService>
,您将能够使用具有自定义 ID 的工厂实例化您的
IRequestService

👍 你永远不会忘记调用

.Init(id)
因为它是函数签名所必需的,编译器不会允许你跳过它。

❗ 如果您的服务是一次性的,您将不得不自己处理。 DI 不会帮助你。

我使用通用主机和控制台应用程序,但它在 Web API 或 ASP.NET MVC 应用程序中的工作方式完全相同。


有用的链接:

© www.soinside.com 2019 - 2024. All rights reserved.