首页 > 解决方案 > 如何使用 .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 实现这一目标?

标签: c#asp.net-coreasp.net-web-apidependency-injection

解决方案


您的问题实际上有两点讨论。一个与代码设计有关,另一个与如何使用 .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 开发人员,这绝对是必读的。


推荐阅读