如何在创建作用域之前从IHostedService注入依赖项



我有一个带后台作业的多租户系统。租赁详细信息存储在数据库中,并且基于服务总线中的租户添加请求,我希望基于租户来解决依赖关系。

为此,我必须在创建作用域之前将依赖项添加到服务集合中。当尝试注入IServiceCollection时,它会给我错误。

我正在寻找从HostedService 注入依赖关系的最佳方式

public async Task MessageHandler(object sender, Message message)
{
// Inject dependencies
services.AddScoped<IMyService,Myservice>(); // No way to get services here

using (var scope = serviceProvider.CreateScope())
{
var ... = scope.ServiceProvider.GetService<...>();
//...
}
}

我不久前也有类似的需求。我创建了自己的服务总线处理程序。

您可以尝试下面的方法,将服务(这里是我使用IMessageService的一个示例(注入到ServiceBus处理程序中,该处理程序本身已经注入了dbcontext。

然后,无论在哪里实现IServiceBusHandler,都可以指定要为哪个租户(及其队列(建立连接。

public class ServiceBusHandler : IServiceBusHandler
{
private readonly ServiceBusSender _serviceBusSender;
private readonly IMessageService _messageService;

public ServiceBusHandler(
ServiceBusSender serviceBusSender,
IMessageService messageService)
{
_serviceBusSender = serviceBusSender;
_messageService = messageService;
}
public async Task PublishMessageAsync<T>(T message)
{
var jsonString = JsonConvert.SerializeObject(message);
var serviceBusMessage = new ServiceBusMessage(jsonString);
await _serviceBusSender.SendMessageAsync(serviceBusMessage);
}
internal static IServiceBusHandler Create(ServiceBusSender sender)
{
return new ServiceBusHandler(sender);
}
}
public class ServiceBusHandlerFactory : IServiceBusHandlerFactory
{
private readonly IAzureClientFactory<ServiceBusClient> _serviceBusClientFactory;
public ServiceBusHandlerFactory(
IAzureClientFactory<ServiceBusClient> serviceBusClientFactory)
{
_serviceBusClientFactory = serviceBusClientFactory;
}
public IServiceBusHandler GetClient(string tenantId)
{
var tenantDetails = _messageService.GetTenantDetails(tenantId); // Call to your DB to get details about the Tenant      
var client = GetServiceBusClient(tenantDetails.QueueName);
var sender = client.CreateSender(tenantDetails.QueueName);
return ServiceBusHandler.Create(sender);
}
protected virtual ServiceBusClient GetServiceBusClient(string queueName)
{
var client = _serviceBusClientFactory.CreateClient(queueName);
return client;
}
}

您试图实现的是在构建容器之后更改注册集。MS.DI不支持这一点,虽然从历史上看,更成熟的DI容器倾向于支持这种行为,但大多数现代DI容器都不再支持这种行为了,因为允许这种行为会带来太多负面后果。例如,Autofac在2016年废除了其Update方法,并详细描述了更新Container的问题。Ninject也经历了类似的过程,尽管在最终发布之前停止了开发,从而消除了更新Container的可能性。Simple Injector DI Container从不支持更新,其文档中有一些明确的文本描述了问题所在

你可能会找到一个支持这一点的DI容器,但我敦促你放弃这条路,因为它可能(也可能(会造成负面后果,正如前面的链接所描述的那样。

相反,你必须找到一种不同的方式来获得租户特定的行为,只需一组注册。这里的技巧通常在于创建IMyService的Proxy实现,该实现可以将调用转发到正确的租户实现。

这可能看起来像这样:

public class ProxyMyService : IMyService
{
public IMyService Service { get; set; }

// IMyService methods
public void SomeMethod() => this.Service.SomeMethod();
}

这个代理类可以在启动时与其他IMyService实现一起注册,如下所示:

services.AddScoped<IMyService, ProxyMyService>();
services.AddTransient<MyServiceTenant1>();
services.AddTransient<DefaultMyServiceTenant>();

这样,您的托管服务可以变成以下内容:

private ProxyMyService service;
public MyHostedService(IMyService service)
{
this.service = (ProxyMyService)service;
}
public async Task MessageHandler(object sender, Message message)
{
using (var scope = serviceProvider.CreateScope())
{
var p = scope.ServiceProvider;
var proxy = (ProxyMyService)p.GetRequiredService<IMyService>();
proxy.Service = IsTentant1
? p.GetRequiredService<MyServiceTenant1>()
: p.GetRequiredService<DefaultMyServiceTenant>();

var ... = p.GetRequiredService<...>();
//...
}
}

一个更进化的解决方案将需要一个代理实现,该实现允许在内部的租户特定实现之间切换。这可能意味着将当前位于MessageHandler内部的部分逻辑移动到ProxyMyService中。

请注意,我建议的解决方案不需要抽象的工厂。抽象工厂通常是不需要的。

最新更新