Home > Software engineering >  How do I register generic Action or Command handlers and then call the right one when the type is de
How do I register generic Action or Command handlers and then call the right one when the type is de

Time:06-04

I'm looking for a way to implement the following.

I want to be able to register "ActionHandlers" as services in my DI Container, for this purpose I have made the following interfaces:

public interface IActionHandler {
  Task HandleAsync(IAction action);
}

public interface IActionHandler<T> : IActionHandler where T : IAction, new() {
  Task HandleAsync(T action);
}

My idea was then to create a derived type called ActionHandlerContainer:

public class ActionHandlerContainer : IActionHandler {
  private readonly Dictionary<Type, IActionHandler> _handlers;


  public ActionHandlerContainer(IEnumerable<IActionHandler<??T??>>) {

   // What to do here?.
   // As this ActionHandlerContainer class is registered as a service,
   // I expect the DI container in return to inject all my ActionHandler services here.
  }
  public Task HandleAsync(IAction action) {

    // Fetch appropriate handler from the map.
    _handlers.TryGetValue(action.getType(), out var actionHandler);
    
    if(actionHandler == null) {
      throw new Exception($"No handler could be found for the Action of type: {action.GetType()}");
    }

    // Invoke the correct handler.
    return actionHandler.HandleAsync(action);

  }
}

Which would take in any action and delegate it to the correct handler, an example handler could look like:

public class SetColorActionHandler : IActionHandler<SetColorAction> {

public async Task HandleAsync(SetColorAction action) {
  return await ComponentManager.SetComponentColor(action);
}

}

The DI container service registration would look like this (I suppose)

builder.Services.AddTransient<IActionHandler<SetColorAction>, SetColorActionHandler>();
builder.Services.AddSingleton<ActionHandlerContainer>();

Some open questions I have myself is:

  • Should multiple ActionHandlers be able to be registered to one action..
  • Is it possible to implement the decorator pattern on top of this, say I want a ConsoleDebugLoggingSetColorActionHandler which decorates the original SetColorActionHandler.

A problem I have right now is if IActionHandler implements IActionHandler, then any IActionHandler implementation has to implement the code async Task HandleAsync(IAction action) method which seems duplicative.

So my question is, given the code examples and explanations, how can I implement this properly?

Thanks in advance, any help is appreciated, Nicky.

[EDIT1]: I tried the following in ActionHandlerContainer::HandleAsync

public Task HandleAsync(IAction action) {
  Type runtimeType = action.GetType();
  var _handler = _serviceProvider.GetService(typeof(IActionHandler<runtimeType>));
}

But this does not seem to work.

[EDIT2]: Providing some context for vendettamit:

public class MqttClientWrapper {

...<omitted>

private Task ClientOnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg) {

Console.WriteLine("The client received an application message.");
        arg.DumpToConsole();
        
        // Create the ActionPayload from the MQTT Application Message's Payload.
        var actionPayload = ActionPayload.FromPayload(arg.ApplicationMessage.Payload);
        
        // Grab the correct action type from the map according to the identifier in the payload.
        var actionType = ActionMap.ActionIdentifierToActionType[actionPayload.ActionIdentifier];
        
        // Now instruct the factory to instantiate that type of action.
        var action = _actionFactory.CreateAction(actionPayload.ActionData, actionType);
        
        // Finally, pass on the action to the correct handler.
        _actionHandlerContainer.HandleAsync(action);

        return Task.CompletedTask;

}

}

CodePudding user response:

One suggestion: Use MediatR. In the past I've railed against it, but I've softened. It's not perfect, but it's a thorough implementation of what you're trying to solve.

Or, here's a detailed custom approach.


As you noted in your question, the issue is that if you're going to have a collection of IActionHandlers<T> but T is different for each one, then what is the type of the collection?

Any solution will involve some sort of reflection or type-checking. (That's inevitable. You're starting with an IAction, but you don't want an IActionHandler<IAction> - you want individual handlers for specific implementations of IAction.) Even though I don't usually use object, it's okay as long as my code ensures that the object will be the expected type. That is, given type T, you're going to get an object which can be cast as IActionHandler<T>.

Here's an approach. I use the terms "command" and "command handler" instead of "action" and "action handler." There's some reflection involved, but it does the job. Even if it's not exactly what you need, it might give you some ideas.

First, some interfaces:

    public interface ICommand
    {
    }

    public interface ICommandHandler
    {
        Task HandleAsync(ICommand command);
    }

    public interface ICommandHandler<TCommand> where TCommand : ICommand
    {
        Task HandleAsync(TCommand command);
    }
  • ICommand marks a class which is used as a command.
  • ICommandHandler is the interface for a class that takes any ICommand and "routes" it to a specific command handler. It's the equivalent of IActionHandler in your question.
  • ICommandHandler<T> is the interface for the type-specific command handler.

Here's the implementation of ICommandHandler. This has to

  • receive a command
  • resolve an instance of the concrete handler for that command type
  • invoke the handler, passing the command to it.
    public class CommandHandler : ICommandHandler
    {
        private readonly Func<Type, object> _getHandler;

        public CommandHandler(Func<Type, object> getHandler)
        {
            _getHandler = getHandler;
        }

        public async Task HandleAsync(ICommand command)
        {
            var commandHandlerType = GetCommandHandlerType(command);
            var handler = _getHandler(commandHandlerType);
            await InvokeHandler(handler, command);
        }

        private Type GetCommandHandlerType(ICommand command)
        {
            return typeof(ICommandHandler<>).MakeGenericType(command.GetType());
        }

        // See the notes below. This reflection could be "cached"
        // in a Dictionary<Type, MethodInfo> so that once you find the "handle" 
        // method for a specific type you don't have to repeat the reflection.
        private async Task InvokeHandler(object handler, ICommand command)
        {
            var handlerMethod = handler.GetType().GetMethods()
                .Single(method => IsHandleMethod(method, command.GetType()));
            var task = (Task)handlerMethod.Invoke(handler, new object[] { command });
            await task.ConfigureAwait(false);
        }

        private bool IsHandleMethod(MethodInfo method, Type commandType)
        {
            if (method.Name != nameof(ICommandHandler.HandleAsync)
                || method.ReturnType != typeof(Task))
            {
                return false;
            }

            var parameters = method.GetParameters();
            return parameters.Length == 1 && parameters[0].ParameterType == commandType;
        }
    }

Here's what this does when public async Task HandleAsync(ICommand command) is called:

  • Determines the generic type of the handler for the command. If the command type is FooCommand then the generic command handler type is ICommandHandler<FooCommand>.
  • Calls Func<Type, object> _getHandler to get a concrete instance of the command handler. That function is injected. What is the implementation of that function? More on that later. But the point is that as far as this class is concerned, it can pass the handler type to this function and get back a handler.
  • Finds the "handle" method on the handler type.
  • Invokes the handle method on the concrete handler, passing the command.

There's room for improvement here. Once it finds the method for a type it could add it to a Dictionary<Type, MethodInfo> to avoid that reflection again. It could even create a function that performs the whole invocation and add it to a Dictionary<Type, Func<Object, Task>. Either of those would improve performance.

(If that sounds convoluted, then once again that's a reason to consider using MediatR. There are a few details about it that I don't like, but does all this work. It also handles more complex scenarios like handlers that return something or handlers that use a CancellationToken.)

That leaves the question - what is the Func<Type, object> that takes a command type and returns the correct command handler?

If you're using IServiceCollection/IServiceProvider, these extensions register everything and provide that. (The point is that injecting the function means that we're not tied to that specific IoC container.)

    public static class CommandHandlerServiceCollectionExtensions
    {
        public static IServiceCollection AddCommandHandling(this IServiceCollection services)
        {
            services.AddSingleton<ICommandHandler>(provider => new CommandHandler(handlerType =>
                {
                    return provider.GetRequiredService(handlerType);
                }
            ));
            return services;
        }

        public static IServiceCollection AddHandlersFromAssemblyContainingType<T>(this IServiceCollection services)
            where T : class
        {
            var assembly = typeof(T).Assembly;
            IEnumerable<Type> types = assembly.GetTypes().Where(type => !type.IsAbstract && !type.IsInterface);
            foreach (Type type in types)
            {
                Type[] typeInterfaces = type.GetInterfaces();
                foreach (Type typeInterface in typeInterfaces)
                {
                    if (typeInterface.IsGenericType && typeInterface.GetGenericTypeDefinition() == typeof(ICommandHandler<>))
                    {
                        services.AddScoped(typeInterface, type);
                    }
                }
            }
            return services;
        }
    }

The first method registers CommandHandler as the implementation of ICommandHandler. The implementation of Func<Type, object> is

handlerType =>
{
    return provider.GetRequiredService(handlerType);
}

In other words, whatever the handler type is, resolve it from the IServiceProvider. If the type is ICommandHander<FooCommand> then it's going to resolve whatever implementation of that interface is registered.

This dependency on the IServiceProvider at runtime is not a service locator. (Everything ultimately depends on it at runtime.) CommandHandler depends on an abstraction - a function - not IServiceProvider. The use of the IoC container is all in the composition root.

You could manually register each one of those implementations:

serviceCollection.AddScoped<ICommandHander<FooCommand>, FooCommandHandler>();

...etc. The second extension does that for you. It discovers implementations of ICommandHandler<T> and registers them with the IServiceCollection.

I have used this in production code. If I wanted it to be more robust I'd add handling for cancellation tokens and maybe return types. (I could be lazy and argue that return types violate command/query separation.) It would require updating how InvokeHandler selects a method on the handler to invoke.

And because nothing is complete without tests, here's a test. It's convoluted. It creates a command which contains both a list and a number. The command handler adds the number to the list. The point is that it's observable. The test only passes if the handler gets registered, resolved, and invoked so that the number gets added to the list.

    [TestClass]
    public class CommandHandlerTests
    {
        [TestMethod]
        public async Task CommandHandler_Invokes_Correct_Handler()
        {
            var serviceCollection = new ServiceCollection();
            serviceCollection
                .AddCommandHandling()
                .AddHandlersFromAssemblyContainingType<AddNumberToListCommand>();

            var serviceProvider = serviceCollection.BuildServiceProvider();
            var commandHandler = serviceProvider.GetRequiredService<ICommandHandler>();

            var list = new List<int>();
            var command = new AddNumberToListCommand(list, 1);

            // This is the non-generic ICommandHandler interface
            await commandHandler.HandleAsync(command);
            Assert.IsTrue(list.Contains(1));
        }
    }

    public class AddNumberToListCommand : ICommand
    {
        public AddNumberToListCommand(List<int> listOfNumbers, int numberToAdd)
        {
            ListOfNumbers = listOfNumbers;
            NumberToAdd = numberToAdd;
        }

        public List<int> ListOfNumbers { get; }
        public int NumberToAdd { get; }
    }

    public class AddNumberToListHandler : ICommandHandler<AddNumberToListCommand>
    {
        public Task HandleAsync(AddNumberToListCommand command)
        {
            command.ListOfNumbers.Add(command.NumberToAdd);
            return Task.CompletedTask;
        }
    }

CodePudding user response:

So what I like:

You're thinking "Open/Closed Principle". That's good.

What I don't like. Your Dictionary has "Type" as a Key. Thus you're trying to store EVERY Type to its 1:N IActionHandler's in one place. (By the way, you'll probably get a Key-Exists error (or it would just overwrite the single) if you tried to register 2:N IActionHandlers for a single Type.

I would move toward:

public class ActionHandlerContainer<T> : IActionHandler<T> { 
  private readonly IDictionary<int, IActionHandler<T>> _handlers;

and "inject" your specific to a single T 1:N concrete handlers.

Notice, I've removed the "Type", and changed to an int? Why an int? Because if you want to control the ORDER of the injected items. You can loop over the IDictionary (int) Keys (in order).

So how would this play out?

You don't register a single ActionHandlerContainer (with all the types)

You register something like

(ioc register the below)

  ActionHandlerContainer<Employee> 

and you constructor inject your 1:N EmployeeHandler(s) into the above.

then you register something like (a different "Type")

(ioc register the below)
ActionHandlerContainer<Candy>
   

You are not using Generics to be a "Type holder of everything". You use Generics to reduce copies of code. (so you don't have to write a "copy" for just Employee, and another copy for "Candy".....

Somewhere you need to inject the

   ActionHandlerContainer<Employee> ahce

...

public EmployeeManager : IEmployeeManager

 private readonly ActionHandlerContainer<Employee> theAhce;

  public EmployeeManager(ActionHandlerContainer<Employee> ahce)

{ this.thheAhce = ahce; /* simple code, you should actually check for null on the input ahce to make sure its not null */ }

public void UpdateEmployee(Employee emp)

{ this.theAhce.Invoke(emp); /* << and this will of course run your 1:N EMPLOYEE handlers */ }

Something like that.

IMHO, get rid of the "holds ALL types" mentality.

  • Related