DI 本身是一个非常简单的话题,但即使存在一些可能会让您感到惊讶的东西。 我希望你能在这里学到新的东西。 事不宜迟,让我们开始吧……
1 IServiceScopeFactory
大多数初级开发人员被告知有 3 个服务生命周期:
- Transient - 每次解决时都会创建服务
- Scoped - 每个请求创建一次服务
- Singleton - 每个应用程序创建一次服务
它只是部分正确。 关于 Scoped 生命周期的真实情况,它不受请求的限制。 它只是发生了,为请求创建了一个范围。 但是,还有更多关于它的内容。 您可以使用 IServiceScopeFactory 创建自己的范围:
public class HomeController : ControllerBase
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public HomeController(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
[HttpGet]
public async Task Index()
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var service = scope.ServiceProvider.GetRequiredService<IService>();
service.DoJob();
}
// asynchronously Disposes service if supported
await using (var asyncScope = _serviceScopeFactory.CreateAsyncScope())
{
var service = asyncScope.ServiceProvider.GetRequiredService<IService>();
service.DoJob();
}
}
}
在上面的示例中,一个范围内的 IService 实例将为单个请求中的每个范围创建两次。
当您需要在 Singleton 中注入 Scoped 服务以及其他依赖注入范围与上下文生命周期不一致的情况时,这尤其有用。
2 使用多个接口注册服务
每当您有一个实现多个接口的类时:
interface IMessagePublisher { ... }
interface IMessageConsumer { ... }
class MessabeBus : IMessagePublisher, IMessageConsumer { ... }
并且应该由每个单独解决,只有一种真正的方法可以做到:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<MessageBus>();
services.AddScoped<IMessagePublisher>(sp => sp.GetRequiredService<MessageBus>());
services.AddScoped<IMessageConsumer>(sp => sp.GetRequiredService<MessageBus>());
}
. . .
}
您需要注册一个服务本身和解析该实例的两个接口。
但是,这里很容易出错:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMessagePublisher, MessageBus>();
services.AddScoped<IMessageConsumer, MessageBus>();
}
. . .
}
使用上面的配置,您最终将为每个接口拥有不同的实例。
而这个错误是我自己犯的。 你能看出这里有什么问题吗?
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
var messageBus = new MessageBus();
services.AddScoped<IMessagePublisher>(sp => messageBus);
services.AddScoped<IMessageConsumer>(sp => messageBus);
}
. . .
}
MessageBus 是在我们的 DI 机制之外创建的。 尽管它已注册为 Scoped,但该单个实例的行为将像一个单例。
不要像我一样,不要重蹈我的覆辙
3 注入多个服务
没有什么能真正阻止您使用相同的界面注册多个服务:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<INotificationHandler, SmsNotificationHandler>();
services.AddScoped<INotificationHandler, EmailNotificationHandler>();
services.AddScoped<INotificationHandler, SkypeNotificationHandler>();
}
. . .
}
此外,它实际上可以非常方便地使用诸如责任链服务之类的模式,可以一次性解决所有问题。 要注入多个实例,请使用 IEnumerable<>:
class NotificationService : INotificationService
{
private readonly IEnumerable<INotificationHandler> _handlers;
// what are thoseeee!
public NotificationService(IEnumerable<INotificationHandler> handlers)
{
_handlers = handlers;
}
public void Handle(MessageDto message)
{
foreach (var handler in _handlers.Where(h => h.CanHandle(message)))
{
handler.Handle(message);
}
}
}
4 尝试注册
如果您的代码中不需要多个服务,而只需要一个服务怎么办。 但是,有人不小心注册了两次。
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(sp => new MyService(index: 1));
services.AddScoped(sp => new MyService(index: 2));
services.AddScoped(sp => new MyService(index: 3));
}
. . .
}
这可能会引起很多麻烦,尤其是当服务包含由于错误的生命周期而消失的状态时。 你能从上面的例子中找出哪个服务会被解析吗? 最新注册的服务将始终具有最高优先级,生命周期在这里无关紧要,请参阅:
[HttpGet]
public string Index()
{
return _myService.WhoDaFakAreU(); // 3
}
为避免数小时的调试,请使用 TryAddXXX 方法集:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(sp => new MyService(index: 1));
services.TryAddScoped(sp => new MyService(index: 2));
services.TryAddScoped(sp => new MyService(index: 3));
}
. . .
}
我们现在很安全。 只有第一个服务会在我们的 DI 容器中注册。
5 解决一个服务多个实现
它可能旨在对一个接口进行多种实现。
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMessageBus, RabbitMqMessageBus>();
services.AddScoped<IMessageBus, AzureMessageBus>();
}
. . .
}
你想区分这些。
我们已经看到的一种方式,但现在在 Linq 的帮助下:
public class MyService : ControllerBase
{
private readonly IMessageBus _messageBus;
public MyService(IEnumerable<IMessageBus> collection)
{
_messageBus = collection.OfType<RabbitMqMessageBus>().Single();
}
. . .
}
就个人而言,我不喜欢它。 您需要注入集合以获取单个项目。 不仅看起来很糟糕,而且在测试时也不方便。 您依赖于直接实施。
撇开我的偏好不谈,当具体实例被标记为内部并位于另一个库中时,您的架构也无法实现。
另一种方法是添加更多接口:
interface IMessageBus { ... }
// marker interfaces
// does not have any implementation, just helps us with DI
interface IRabbitMqMessageBus : IMessageBus { /*empty here*/ }
interface IAzureMessageBus : IMessageBus { }
class RabbitMqMessageBus : IRabbitMqMessageBus { ... }
class AzureMessageBus : IAzureMessageBus { ... }
并使用这些新接口注册和解决它:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IRabbitMqMessageBus, RabbitMqMessageBus>();
services.AddScoped<IAzureMessageBus, AzureMessageBus>();
}
. . .
}
或者第三种选择,如果你真的想成为一个聪明人,你可以使用工厂
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<RabbitMqMessageBus>();
services.AddScoped<AzureMessageBus>();
services.AddTransient<Func<MessageBusType, IMessageBus>>(serviceProvider => key =>
{
return key switch
{
MessageBusType.RabbitMq => serviceProvider.GetService<RabbitMqMessageBus>(),
MessageBusType.Azure => serviceProvider.GetService<AzureMessageBus>(),
_ => throw new InvalidEnumArgumentException(),
};
});
}
. . .
}
随着它的时髦用法:
public class MyService
{
private readonly IMessageBus _messageBus;
public MyService(Func<MessageBusType, IMessageBus> messageBusFactory)
{
_messageBus = messageBusFactory(MessageBusType.RabbitMq);
}
. . .
}
您可以决定哪一种最适合您的需求。
关注七爪网,获取更多APP/小程序/网站源码资源!