c#:DI 和附加参数

c#: DI and additional parameter

提问人:Oleg Sh 提问时间:1/29/2023 最后编辑:KitOleg Sh 更新时间:2/28/2023 访问量:981

问:

例如,我有一个具有以下依赖项的类: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呢?例如,我想传递给 ctor,但不能使用依赖注入器将其描述为依赖关系(在我的情况下)deviceIdProgram.cs

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

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

并在那里添加逻辑。

然后我必须在使用_service方法之前调用。它有效,但所有缺点和潜在问题显然都是(如果忘记打电话等)_service.Init(...);

如何使用 DI 传递此参数?

C# 依赖注入 参数 造函数 传递

评论

0赞 jeb 2/21/2023
我在 DI 方面没有回答您的问题,但我使用 IHostedService 调用 init() 方法解决了类似的问题。因此,在您的情况下,这将是一个 CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeServiceLuncher 服务,它在启动时调用 CreateAutoDeletingRequestReachSuspensionDaysLimitAndInactiveLongTimeService 的 InitMethod。也许这也适用于您的要求。
0赞 Jeremy Lakeman 2/23/2023
恕我直言,遵循“选项模式”。使用数据值定义一个类。将其添加为依赖项。然后从设置文件加载该类。或者编写另一个服务来填充值。learn.microsoft.com/en-us/aspnet/core/fundamentals/......IOptions<>
0赞 Vlad DX 2/27/2023
请查看我的回答。如果它不能回答您的问题,请发表评论,我会更新它。
0赞 Vlad DX 2/27/2023
我是否正确理解您想根据请求在运行时定义一个?deviceId
0赞 Vlad DX 3/1/2023
请问您介意查看答案并标记正确答案吗?

答:

3赞 Kit 2/23/2023 #1

执行此操作的典型方法是不注入任何基元,而是注入唯一的配置类 (),以避免参数类型发生冲突,或者注入以利用 .NET 配置支持。SomethingSomethingSettingsIOptions<SomethingSomethingSettings>

另一种方法是注入一个可以在运行时使用的工厂类,该类具有执行所需操作的所有逻辑。因此,您将拥有一个成员,而不是一个方法和一个成员,其方法将根据需要调用,并且具有它需要的上下文(同样,通常来自类。Init_device_factoryCreateSomethingSomethingSettings

4赞 hcerim 2/24/2023 #2

你可以做这样的事情:

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”、“banana”等)。

评论

0赞 Oleg Sh 4/29/2023
No-no,不是一个静态字段deviceId
0赞 hcerim 5/3/2023
你甚至明白什么是依赖注入吗?如果需要动态值,只需向函数添加附加参数即可。为什么要为这种行为污染构造函数?
1赞 Gabor 2/25/2023 #3

如果你不是来自应用程序设置,而是运行时的动态值,那么可以帮助你。deviceIdAutofac

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

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

在 .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);

1赞 Vlad DX 2/27/2023 #4

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

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

📦 安装 NuGet 包:Install NuGet packages:

  • Microsoft.Extensions.DependencyInjection.抽象,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魔术的主要部分

    工厂功能将执行:

    • 将 id 作为输入。int
    • 通过 DI 实例化(本质上,将创建一个对象)。IRequestServiceRequestService
    • ID 值作为参数传递的调用。.Init(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);
    

    注意:和将是相同的实例。dependencyrequestService.Dependency


因此,如果在任何地方注入 Func<int、IRequestService>则可以使用具有自定义 ID 的工厂实例化 IRequestService

👍 你永远不会忘记调用,因为它是函数签名所要求的,编译器不允许你跳过它。.Init(id)

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

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


有用的链接:

评论

0赞 Oleg Sh 4/29/2023
谢谢!您的解决方案非常灵活!和我问的一样!
0赞 Vlad DX 5/23/2023
@OlegSh,很乐意帮忙。我真的很喜欢 .NET DI :) 的美丽
0赞 Semih 2/28/2023 #5

你可以做这样的事情:

// Singleton, Scoped, Transient...
services.AddSinleton<IYourInterface, YourClass>();
services.AddSinleton<IYourInterface2, YourClass2>();

然后你可以使用任何类CTOR

private readonly IYourInterfaceService _yourInterfaceService;
private readonly IYourInterfaceService2 _yourInterfaceService2;

public PersonExpenseController(IYourInterfaceService yourInterfaceService, IYourInterfaceService2 yourInterfaceService2)
{
    _yourInterfaceService = yourInterfaceService;
    _yourInterfaceService2 = yourInterfaceService2
}

}