简体   繁体   中英

How to register multiple implementation for same interface using .NET CORE DI

I have an Interface called ICompleteConsumer which has only one method and I have multiple implementations for that interface.

EX:

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
 }
}

I registered these in startUp like this

services.AddScoped<ICompleteConsumer, SampleClass1>(),
services.AddScoped<ICompleteConsumer, SampleClass2>(), 

I am inject these dependencies as below

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;
 }
}

How do I achieve this using .net core DI?

There are actually two points of discussion in your question. One is a point related to the design of the code, the other one is a point related on how to use the .NET core DI container in order to handle the required registrations. Both of them are important, but we need to treat them one at a time.

How to organize the code

To solve your problem in a clean and extensibile way you need to use a design pattern known as the composite design pattern. In order to do so, you need to change the definition of your interface to the following:

public interface IMessageConsumer 
{
  bool CanHandleMessage(Message message);
  Task HandleMessage(Message message);
}

Your interface implementations are then changed as follows:

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)}");
  }
}

At this point you need to introduce a special message consumer, which will be used to dispatch the message to the proper consumer. This is called the composite message consumer and this is the implementation of IMessageConsumer that you will register in your DI container and that will be injected in all the classes which need a message consumer in order to do their business .

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);
  }
}

Here is an example of a class which uses the IMessageConsumer interface. At runtime, the DI container will inject an instance of 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...
  }
}

How to register the services on the .NET core DI container

Deciding the proper lifetime for your registrations is a problem that goes beyond the scope of this discussion.

In my example code above I have defined stateless consumer classes and the composite consumer only iterates over the array of the available consumers. The array is never modified during the iteration. This means that all the involved classes are thread safe, so we can register all of them with a singleton lifetime.

That said, the simplest registration that you can perform is the following:

// 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
    });
});

A great book to learn about these topics is this one . If you are a .NET developer this is definitely a must read.

Try this:

private readonly IEnumerable<ICompleteConsumer> completeConsumers;

 public ConsumerHandlerService(IEnumerable<ICompleteConsumer> completeConsumers) {
  this.completeConsumers = completeConsumer
 }


...

//Get the service you want basing on the messagetype, I guess
var completeConsumer = this.completeConsumers.First(c => c.Type == MessageType); // adjust this to your needs

 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;
 }

You can get multiple service by IEnumerable

public class ConsumerHandlerService
{
    private readonly IEnumerable<ICompleteConsumer> _completeConsumers;

    public ConsumerHandlerService(IEnumerable<ICompleteConsumer> completeConsumers)
    {
        this.completeConsumers = completeConsumers;
    }

    public void DoSomeWork(string message)
    {
        var model = JsonConvert.DeserializeObject<Model>(message);
        foreach (var completeConsumer in completeConsumers)
        {
            completeConsumer.Complete(model);
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM