0

I have RegisterMessageHandlers that register message handler per message name. I want to automatically find and register all handlers through reflection. I can annotate each message with MessageAttribute (like shown below) and get the message name through reflection as well. The problem is when I want to instantiate a handler via Activator I have to provide all the dependencies in the constructor.

My solution is to register all instances with DI and pass IServiceProvider to MainManagerClass(IServiceProvider provider) and then use ActivatorUtilities.CreateInstance(handlerType) to instantiate each discovered handler through reflection.

But, then I read that there is DI and service locator which is antipattern and it's not very clear when one becomes the other.

So, I think I need a DI in order to accomplish what I want. Or do I?

public class MainManagerClass
{
    private Dictionary<string, MessageHandler> _handlers;

    private void RegisterMessageHandlers()
    {
        _messageHandlers["msg1"] = new MessageHandler1(new Service1());
        _messageHandlers["msg2"] = new MessageHandler2(new Service1(), new Service2());
    }
}
   
public class MessageHandler1 : MessageHandler<Message1>
{
    public MessageHandler1(IService1 service){}
}

public class MessageHandler2 : MessageHandler<Message2>
{
   public MessageHandler1(IService1 service1, IService2 service2){}
}

public abstract class MessageHandler<T> : MessageHandler
{
}

public abstract class MessageHandler
{
}

[Message("msg1")]
public class Message1
{
}

UPDATE

  public class MainManagerClass
  {
       private Dictionary<string, MessageHandler> _handlers;
       private readonly IServiceProvider _serviceProvider;


       public MainManagerClass(IServiceProvider serviceProvider)
       {

       }
  
       private void RegisterMessageHandlers()
       {         
           var messageHandlers = Assembly.GetCallingAssembly()
               .GetTypes()
               .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(MessageHandler))).ToList();
           foreach (var handler in messageHandlers)
           {
               var messageType = handler.BaseType.GenericTypeArguments[0];
               var msgAttribute = messageType.CustomAttributes
                        .Where(t => t.AttributeType == typeof(MessageAttribute))
                        .FirstOrDefault();
               if (msgAttribute == null)
                   throw new Exception($"Message name not defined for message type {messageType.Name}");
    
               var msgName = msgAttribute.ConstructorArguments[0].Value.ToString();
    
               _messageHandlers[msgName] = ActivatorUtilities.CreateInstance(_serviceProvider, handler) as MessageHandler;
           }
        }
  }

internal class Program
{      
     static async Task Main(string[] args)
     {      
           var host = CreateHostBuilder().Build();
           var manager = new MainManagerClass(host.Services);
           ...
     }

     private static IHostBuilder CreateHostBuilder()
     {
         return Host.CreateDefaultBuilder()
            .ConfigureServices((_, services) =>
            {
                services
                    .AddSingleton<IService1, Service1>()
                    .AddSingleton<IService2, Service2>()
                    .AddSingleton<IService3, Service3>()
                    ;
            });
    }
}
theateist
  • 13,879
  • 17
  • 69
  • 109
  • "... automatically ... through reflection" triggers my alarm bells. Is this really needed or just nice magic? – Klamsi Jan 31 '23 at 07:24
  • @Klamsi, why it triggers your alarm bells? What's wrong with having "nice magic" so I want have to update/maintain `RegisterMessageHandlers` every time I add a new message handler? – theateist Jan 31 '23 at 07:35

2 Answers2

0

If you don't want to add interface and keep abstract class as in your example - you can register your handlers like that:

builder.Services.AddTransient<MessageHandler<Message1>, MessageHandler1>();
builder.Services.AddTransient<MessageHandler<Message2>, MessageHandler2>();

And then if you inject MessageHandler<Message1> in your controller for instance, MessageHandler1 will be resolved as well as other dependencies (IService1 in your case).

Jaroslav
  • 191
  • 1
  • 12
  • I know how to use DI and I can register services like you showed. That wasn't the question. Anyway, the whole point was to write a message handler and it will automaticaly registered. I don't want to register it myself. That's why I want to use reflection to get all handlers and then use `ActivatorUtilities.CreateInstance` to instantiate them – theateist Jan 31 '23 at 07:47
  • I updated my post with what I meant by "automatic" registration – theateist Jan 31 '23 at 15:52
  • @theateist have you checked this post - [LINK](https://stackoverflow.com/questions/68636366/how-to-register-transient-services-via-custom-attribute-and-retrieve-them-during)? There handlers are being searched by the attribute and then registered. I believe you could join your approach and the one in the accepted answer. – Jaroslav Feb 01 '23 at 12:10
0

I think I still don't get your question but I hope this will be helpfull for you. Anyway, I followed Jaroslav's answer and your comment to create MessageHandlerFactory object then I changed your code a bit to cover both scenarios but you need the message type to resolve its handler from the service provider plus, If you want to register all handlers automatically, you should use reflection at startup, find all objects that inherit from IMessageHandler<T>, and register them to DI or use packages like Scrutor.

public class MessageHandlerFactory
{
    private IServiceProvider _serviceProvider;

    public IMessageHandler<T> ResolveHandler<T>()
    {
        return _serviceProvider.GetRequiredService<IMessageHandler<T>>();
    }
}
   
public class MessageHandler1 : IMessageHandler<Message1>
{
    public MessageHandler1(IService1 service){}
}

public class MessageHandler2 : IMessageHandler<Message2>
{
   public MessageHandler2(IService1 service1, IService2 service2){}
}

public interface IMessageHandler<T> 
{
}


public class Message1
{
}

public class Message2
{
}

builder.Services.AddScoped<IMessageHandler<Message1>, MessageHandler1>();
builder.Services.AddScoped<IMessageHandler<Message2>, MessageHandler2>();