提问人:Oleg Sh 提问时间:1/29/2023 最后编辑:KitOleg Sh 更新时间:2/28/2023 访问量:981
c#:DI 和附加参数
c#: DI and additional parameter
问:
例如,我有一个具有以下依赖项的类: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,但不能使用依赖注入器将其描述为依赖关系(在我的情况下)deviceId
Program.cs
我必须像这样创建“Init”方法:
public void Init(int deviceId)
{
_device = _deviceService.GetDeviceById(deviceId);
// ...
}
并在那里添加逻辑。
然后我必须在使用_service方法之前调用。它有效,但所有缺点和潜在问题显然都是(如果忘记打电话等)_service.Init(...);
如何使用 DI 传递此参数?
答:
执行此操作的典型方法是不注入任何基元,而是注入唯一的配置类 (),以避免参数类型发生冲突,或者注入以利用 .NET 配置支持。SomethingSomethingSettings
IOptions<SomethingSomethingSettings>
另一种方法是注入一个可以在运行时使用的工厂类,该类具有执行所需操作的所有逻辑。因此,您将拥有一个成员,而不是一个方法和一个成员,其方法将根据需要调用,并且具有它需要的上下文(同样,通常来自类。Init
_device
_factory
Create
SomethingSomethingSettings
你可以做这样的事情:
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”等)。
评论
deviceId
如果你不是来自应用程序设置,而是运行时的动态值,那么可以帮助你。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
)
);
}
}
若要与内置 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);
✅ 你可以像这样使用 .NET 6 依赖项注入来做到这一点。
首先,创建一个 .NET 6 控制台应用(包含顶级语句)并添加必要的 NuGet 包。
📦 安装 NuGet 包:Install NuGet packages:
- Microsoft.Extensions.DependencyInjection.抽象,6.0.0
- Microsoft.Extensions.Hosting,6.0.0
添加使用:
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection;
创建一个可以注入到我们的服务和其他地方的依赖项:
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; } }
使用每个范围的自定义 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); }
创建主机生成器:
var builder = Host.CreateDefaultBuilder(); // You can do exactly the same with an `WebApplicationBuilder` // WebApplication.CreateBuilder(args);
开始配置 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 实例化(本质上,将创建一个对象)。
IRequestService
RequestService
- 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; }); });
- 将 id 作为输入。
之后,只需构建主机并创建一个 DI 范围:
var host = builder.Build(); var scope = host.Services.CreateScope();
最后,使用 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 或 MVC 应用程序 ASP.NET 工作方式完全相同。
有用的链接:
评论
你可以做这样的事情:
// 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
}
}
评论
IOptions<>
deviceId