Simple Injector usage for generic command handler

2019-03-20 07:08发布

问题:

The interfaces,commands and command handler set up as per instructions in Simpleinjector wiki.

public interface ICommand
{
    string Name { get; set; }
}

public class Command1 : ICommand
{
    public string Name { get; set; }
}

public class Command2 : ICommand
{
    public string Name { get; set; }
}

public interface ICommandHandler<TCommand>
{
    void Execute(TCommand Command);
}

public class Command1Handler : ICommandHandler<Command1>
{
    public void Execute(Command1 Command) {
        Console.WriteLine(Command.Name);
    }
}

public class Command2Handler : ICommandHandler<Command2>
{
    public void Execute(Command2 Command) {
        Console.WriteLine(Command.Name + "Hello");
    }
}

Decorator:

public class CommandDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> _handler;

    public CommandDecorator(ICommandHandler<TCommand> handler)
    {
        this._handler = handler;
    }

    public void Execute(TCommand command)
    {
        this._handler.Execute(command);
    }
}

Sample program

public class Program
{
    static void Main(string[] args)
    {
        Container container = new Container();

        //registering 
        container.RegisterAll<ICommand>(typeof(Command1), typeof(Command2));

        container.RegisterManyForOpenGeneric(
            typeof(ICommandHandler<>),
            typeof(ICommandHandler<>).Assembly);

        container.RegisterDecorator(typeof(ICommandHandler<>), 
            typeof(CommandDecorator<>));

        container.Verify();

        // sample test command 
        ICommand testcommand = new Command2();
        testcommand.Name = "command 1";

        var type = typeof(ICommandHandler<>).MakeGenericType(testcommand.GetType());

        dynamic instance = container.GetInstance(type);
        instance.Execute((dynamic)testcommand);
    }
}

Is this the right way to get the right handler for handling the command at runtime. This is a sample and in the real app the commands are going to posted to a queue and a service is going to read the command and process it . I guess the Decorator has to be used for that but am not able to get it working. please suggest better options if any.

回答1:

Your commands (Command1 and Command2) are not services: they should not be registered. They are runtime data (message) that you pass through your services (your command handlers). So you should remove the Collection.Register<ICommand> (RegisterAll in v2) registration. It is of no use. You already see its of no use, since in your example you are newing the Command2 up manually, which is the right thing to do.

What you are doing in the last three lines of code is dispatching a command of an unknown type to the right command handler registration. You always need some reflection to pull this of, since you need to build the closed ICommandHandler<TCommand> type based on the command type, which is something you don't know at compile time. Instead of using the C# dynamic keyword, you can also use the .NET reflection API, but in my experience using dynamic is better in this particular case. One important downside of the reflection API is that the API will always wrap any thrown exception (in case of a failure) with an InvocationException and that makes it harder to do certain exception handling up the call stack.

So long story short, this should be your registration:

Container container = new Container();

container.Register(
    typeof(ICommandHandler<>),
    typeof(ICommandHandler<>).Assembly);

container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(CommandDecorator<>));

And this should be the dispatching logic:

var type = typeof(ICommandHandler<>).MakeGenericType(command.GetType());

dynamic handler = container.GetInstance(type);
handler.Execute((dynamic)command);