c# - 如何使用 .NET CORE DI 为同一接口注册多个实现
问题描述
我有一个名为 ICompleteConsumer 的接口,它只有一个方法,并且该接口有多个实现。
前任:
public class SampleClass1: ICompleteConsumer {
public async Task Complete(Model model) {
// do the work
}
}
public class SampleClass2: ICompleteConsumer {
public async Task Complete(Model model) {
// do the work
}
}
我像这样在启动中注册了这些
services.AddScoped<ICompleteConsumer, SampleClass1>(),
services.AddScoped<ICompleteConsumer, SampleClass2>(),
我注入这些依赖项如下
public class ConsumerHandlerService(string message) {
private readonly ICompleteConsumer completeConsumer;
public ConsumerHandlerService(ICompleteConsumer completeConsumer) {
this.completeConsumer = completeConsumer
}
switch (MessageType) {
case (MessageType .1) //I want SampleClass1 implementation to here
var model = JsonConvert.DeserializeObject < Model > (message);
await completeConsumer.complete(model);
break;
case (MessageType .2) // I want SampleClass2 implementation to here
var model = JsonConvert.DeserializeObject < Model > (message);
await completeConsumer.complete(model);
break;
}
}
如何使用 .net core DI 实现这一目标?
解决方案
您的问题实际上有两点讨论。一个与代码设计有关,另一个与如何使用 .NET core DI 容器以处理所需的注册有关。两者都很重要,但我们需要一次处理一个。
如何组织代码
要以一种干净且可扩展的方式解决您的问题,您需要使用一种称为复合设计模式的设计模式。为此,您需要将接口的定义更改为以下内容:
public interface IMessageConsumer
{
bool CanHandleMessage(Message message);
Task HandleMessage(Message message);
}
然后您的接口实现更改如下:
public class FooMessageConsumer: IMessageConsumer
{
public bool CanHandleMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
return message.Type == "foo";
}
public Task HandleMessage(Message message)
{
if (message is null)
throw new ArgumentNullException(nameof(message));
if (!this.CanHandleMessage(message))
throw new InvalidOperationException($"{nameof(FooMessageConsumer)} can only handle foo messages.");
await Task.Delay(100).ConfigureAwait(false);
Console.Writeline($"Message {message.Id} handled by {nameof(FooMessageConsumer)}");
}
}
public class BarMessageConsumer: IMessageConsumer
{
public bool CanHandleMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
return message.Type == "bar";
}
public Task HandleMessage(Message message)
{
if (message is null)
throw new ArgumentNullException(nameof(message));
if (!this.CanHandleMessage(message))
throw new InvalidOperationException($"{nameof(BarMessageConsumer)} can only handle bar messages.");
await Task.Delay(100).ConfigureAwait(false);
Console.Writeline($"Message {message.Id} handled by {nameof(BarMessageConsumer)}");
}
}
此时需要引入一个特殊的消息消费者,用于将消息分派给合适的消费者。这称为复合消息消费者,这是您将在 DI 容器中注册的实现IMessageConsumer
,并将注入所有需要消息消费者才能开展业务的类中。
public class CompositeMessageConsumer : IMessageConsumer
{
private readonly IMessageConsumer[] _consumers;
public CompositeMessageConsumer(IEnumerable<IMessageConsumer> consumers)
{
if (consumers is null)
throw new ArgumentNullException(nameof(consumers));
this._consumers = consumers.ToArray();
}
public bool CanHandleMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
return this._consumers.Any(c => c.CanHandleMessage(message));
}
public async Task HandleMessage(Message message)
{
if (message is null)
throw new ArgumentNullException(nameof(message));
if (!this.CanHandleMessage(message))
throw new InvalidOperationException("None of the available consumers is able to handle the provided message.");
var consumer = this._consumers.First(c => c.CanHandleMessage(message));
await consumer.HandleMessage(message).ConfigureAwait(false);
}
}
这是使用该IMessageConsumer
接口的类的示例。在运行时,DI 容器将注入一个CompositeMessageConsumer
.
// this is an example of a class depending on the IMessageConsumer service
public class MessageProcessor
{
// at runtime this will be an instance of CompositeMessageConsumer
private readonly IMessageConsumer _consumer;
// the DI container will inject an instance of CompositeMessageConsumer here
public MessageProcessor(IMessageConsumer consumer)
{
if (consumer is null) throw new ArgumentNullException(nameof(consumer));
this._consumer = consumer;
}
public async Task ProcessIncomingMessage(Message message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
// do all the pre processing here...
// handle the message
await this._consumer.HandleMessage(message).ConfigureAwait(false);
// do all the post processing here...
}
}
如何在 .NET core DI 容器上注册服务
为您的注册决定适当的生命周期是一个超出本讨论范围的问题。
在上面的示例代码中,我定义了无状态消费者类,复合消费者只迭代可用消费者的数组。数组在迭代期间永远不会被修改。这意味着所有涉及的类都是线程安全的,因此我们可以使用单例生命周期注册所有这些类。
也就是说,您可以执行的最简单的注册如下:
// register the consumers as classes
services.AddSingleton<FooMessageConsumer>();
service.AddSingleton<BarMessageConsumer>();
// register the composite message consumer as an interface, so that when you require IMessageConsumer you get CompositeMessageConsumer
services.AddSingleton<IMessageConsumer>(container =>
{
var fooConsumer = container.GetRequiredService<FooMessageConsumer>();
var barConsumer = container.GetRequiredService<BarMessageConsumer>();
return new CompositeMessageConsumer(new IMessageConsumer[]
{
fooConsumer,
barConsumer
});
});
这是一本了解这些主题的好书。如果您是 .NET 开发人员,这绝对是必读的。
推荐阅读
- microsoft-graph-api - https://graph.microsoft.com/beta/me/profile 端点出现 500 错误
- amazon-web-services - Terraform aws_lb_listener_rule 条件“不支持的块类型”
- c# - 将字符串与 XML 匹配,其值存储在多个元素上
- javascript - 在普通 JS 中访问索引
- firebase - 如何将多个域指向同一个网站?
- javascript - 如何使用 signedURLs 和 ReactJS 将文件保存到 AWS?
- sql - 在 SQL 中合并行 COUNTS
- python - 在 Matplotlib 中绘制 X 与 Y,由 Pandas 数据框中的第三个变量着色
- python - 使用前缀列名将每列乘以它们的相对因子
- javascript - 在 svg-pan-zoom 中搜索 SVG