From 91221c02f4653aeeb6121183b3eba4cef08a163c Mon Sep 17 00:00:00 2001 From: Adrian Alonso Date: Mon, 30 May 2016 15:25:20 -0300 Subject: [PATCH] Replaced the usage of dynamic to execute the commands in the command bus We are now using reflection in order to support non public command handler implementations. commandBus.Execute (new MyCommand ()) // void command var result = commandBus.execute (new MyCommandWithResult ()) // command with result --- .../Merq.Commands.Tests/CommandBusSpec.cs | 59 ++++++++-- src/Commands/Merq.Commands/CommandBus.cs | 107 +++++++++++++++--- src/Commands/Merq.Commands/ICommandBus.cs | 8 +- 3 files changed, 150 insertions(+), 24 deletions(-) diff --git a/src/Commands/Merq.Commands.Tests/CommandBusSpec.cs b/src/Commands/Merq.Commands.Tests/CommandBusSpec.cs index 8436a880..032ba04f 100644 --- a/src/Commands/Merq.Commands.Tests/CommandBusSpec.cs +++ b/src/Commands/Merq.Commands.Tests/CommandBusSpec.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Linq; using Moq; using Xunit; @@ -147,7 +148,7 @@ public void when_executing_sync_command_with_result_then_invokes_sync_handler_wi handler.Setup (x => x.Execute (command)).Returns (expected); var bus = new CommandBus(handler.Object); - var result = bus.Execute (command); + var result = bus.Execute (command); Assert.Same (expected, result); } @@ -184,37 +185,37 @@ public async void when_executing_async_command_then_invokes_async_handler_with_r [Fact] public void when_constructing_with_null_handlers_then_throws () { - Assert.Throws (() => new CommandBus (default(IEnumerable))); + Assert.Throws (() => new CommandBus (default (IEnumerable))); } [Fact] public void when_can_handle_with_null_command_then_throws () { - Assert.Throws (() => new CommandBus ().CanHandle(null)); + Assert.Throws (() => new CommandBus ().CanHandle (null)); } [Fact] public void when_can_execute_with_null_command_then_throws () { - Assert.Throws (() => new CommandBus ().CanExecute (null)); + Assert.Throws (() => new CommandBus ().CanExecute (null)); } [Fact] public void when_execute_with_null_command_then_throws () { - Assert.Throws (() => new CommandBus ().Execute (default(Command))); + Assert.Throws (() => new CommandBus ().Execute (default (Command))); } [Fact] public void when_execute_result_with_null_command_then_throws () { - Assert.Throws (() => new CommandBus ().Execute (default(CommandWithResult))); + Assert.Throws (() => new CommandBus ().Execute (default (CommandWithResult))); } [Fact] public async Task when_executeasync_with_null_command_then_throws () { - await Assert.ThrowsAsync (() => new CommandBus ().ExecuteAsync (default(AsyncCommand), CancellationToken.None)); + await Assert.ThrowsAsync (() => new CommandBus ().ExecuteAsync (default (AsyncCommand), CancellationToken.None)); } [Fact] @@ -223,6 +224,28 @@ public async Task when_executeasync_result_with_null_command_then_throws () await Assert.ThrowsAsync (() => new CommandBus ().ExecuteAsync (default (AsyncCommandWithResult), CancellationToken.None)); } + [Fact] + public void when_executing_non_public_command_handler_then_invokes_handler_with_result () + { + var handler = new NonPublicCommandHandlerWithResults (new Result()); + var bus = new CommandBus(handler); + + var results = bus.Execute (new CommandWithResults()); + + Assert.Equal (1, results.Count ()); + } + + [Fact] + public void when_executing_command_as_explicit_ICommand_then_invokes_handler () + { + var handler = new Mock>(); + var command = new Command(); + var bus = new CommandBus(handler.Object); + + bus.Execute ((ICommand)command); + + handler.Verify (x => x.Execute (command)); + } public class AsyncCommand : IAsyncCommand { } @@ -232,6 +255,28 @@ public class Command : ICommand { } public class CommandWithResult : ICommand { } + public class CommandWithResults : ICommand> { } + public class Result { } + + class NonPublicCommandHandlerWithResults : ICommandHandler> + { + Result result; + + public NonPublicCommandHandlerWithResults (Result result) + { + this.result = result; + } + + bool ICanExecute.CanExecute (CommandWithResults command) + { + return true; + } + + IEnumerable ICommandHandler>.Execute (CommandWithResults command) + { + yield return result; + } + } } } diff --git a/src/Commands/Merq.Commands/CommandBus.cs b/src/Commands/Merq.Commands/CommandBus.cs index 0f79b523..094d73b9 100644 --- a/src/Commands/Merq.Commands/CommandBus.cs +++ b/src/Commands/Merq.Commands/CommandBus.cs @@ -59,13 +59,13 @@ public virtual bool CanHandle (IExecutable command) /// /// The command parameters for the query. /// if the command can be executed. otherwise. - public virtual bool CanExecute (IExecutable command) + public virtual bool CanExecute (TCommand command) where TCommand : IExecutable { if (command == null) throw new ArgumentNullException (nameof (command)); - var handler = GetCommandHandler(command); + var handler = GetCommandHandler ((IExecutable) command); - return handler.CanExecute ((dynamic)command); + return ((ICanExecute)handler).CanExecute (command); } /// @@ -76,9 +76,10 @@ public virtual void Execute (ICommand command) { if (command == null) throw new ArgumentNullException (nameof (command)); - var handler = GetCommandHandler(command); - - handler.Execute ((dynamic)command); + ExecuteImpl ( + "ExecuteCommand", + new[] { command.GetType () }, + new[] { command }); } /// @@ -91,9 +92,10 @@ public virtual TResult Execute (ICommand command) { if (command == null) throw new ArgumentNullException (nameof (command)); - var handler = GetCommandHandler(command); - - return handler.Execute ((dynamic)command); + return (TResult)ExecuteImpl ( + "ExecuteCommandWithResult", + new[] { command.GetType (), typeof (TResult) }, + new[] { command }); } /// @@ -105,9 +107,10 @@ public virtual Task ExecuteAsync (IAsyncCommand command, CancellationToken cance { if (command == null) throw new ArgumentNullException (nameof (command)); - var handler = GetCommandHandler(command); - - return handler.ExecuteAsync ((dynamic)command, cancellation); + return (Task)ExecuteImpl ( + "ExecuteCommandAsync", + new[] { command.GetType () }, + new object[] { command, cancellation }); } /// @@ -121,8 +124,66 @@ public virtual Task ExecuteAsync (IAsyncCommand comma { if (command == null) throw new ArgumentNullException (nameof (command)); + return (Task)ExecuteImpl ( + "ExecuteCommandWithResultAsync", + new[] { command.GetType (), typeof (TResult) }, + new object[] { command, cancellation }); + } + + object ExecuteImpl (string methodName, Type[] typeArguments, params object[] parameters) + { + // Important: We need to do this in order to be able to invoke the Execute or ExecuteAsync + // methods without specifying the generic arguments by the caller explicitly + // Otherwise it would not be possible to do the following: + // + // commandBus.Execute (new MyCommand ()) // void command + // var result = commandBus.execute (new MyCommandWithResult ()) // command with result + + try { + return this.GetType () + .GetTypeInfo () + .GetDeclaredMethod (methodName) + .MakeGenericMethod (typeArguments) + .Invoke (this, parameters); + } catch (TargetInvocationException ex) { + // TODO: replace the usage of throwing the inner exception with rethrow preserving stacktrace + throw ex.InnerException; + } + } + + void ExecuteCommand (TCommand command) where TCommand : ICommand + { + if (command == null) throw new ArgumentNullException (nameof (command)); + var handler = GetCommandHandler(command); + handler.Execute (command); + } + + TResult ExecuteCommandWithResult (TCommand command) where TCommand : ICommand + { + if (command == null) throw new ArgumentNullException (nameof (command)); + + var handler = GetCommandHandler (command); + + return handler.Execute (command); + } + + Task ExecuteCommandAsync (TCommand command, CancellationToken cancellation) where TCommand : IAsyncCommand + { + if (command == null) throw new ArgumentNullException (nameof (command)); + + var handler = GetAsyncCommandHandler (command); + + return handler.ExecuteAsync (command, cancellation); + } + + Task ExecuteCommandWithResultAsync (TCommand command, CancellationToken cancellation) where TCommand : IAsyncCommand + { + if (command == null) throw new ArgumentNullException (nameof (command)); + + var handler = GetAsyncCommandHandler (command); + return handler.ExecuteAsync ((dynamic)command, cancellation); } @@ -167,7 +228,7 @@ static string ProcessErrors (List nonGenericHandlers, List Strings.CommandBus.DuplicateHandler (handler.Item2.GetType ().Name, handler.Item1.Name)))); } - dynamic GetCommandHandler (IExecutable command) + ICommandHandler GetCommandHandler (IExecutable command) { ICommandHandler handler; if (!handlerMap.TryGetValue (command.GetType (), out handler)) { @@ -177,6 +238,26 @@ dynamic GetCommandHandler (IExecutable command) return handler; } + ICommandHandler GetCommandHandler (TCommand command) where TCommand : ICommand + { + return (ICommandHandler)GetCommandHandler ((IExecutable)command); + } + + ICommandHandler GetCommandHandler (IExecutable command) where TCommand : ICommand + { + return (ICommandHandler)GetCommandHandler (command); + } + + IAsyncCommandHandler GetAsyncCommandHandler (TCommand command) where TCommand : IAsyncCommand + { + return (IAsyncCommandHandler)GetCommandHandler ((IExecutable)command); + } + + IAsyncCommandHandler GetAsyncCommandHandler (IExecutable command) where TCommand : IAsyncCommand + { + return (IAsyncCommandHandler)GetCommandHandler (command); + } + static Type GetCommandType (Type type) { return type.GetTypeInfo ().ImplementedInterfaces diff --git a/src/Commands/Merq.Commands/ICommandBus.cs b/src/Commands/Merq.Commands/ICommandBus.cs index 726ac04b..67eade0d 100644 --- a/src/Commands/Merq.Commands/ICommandBus.cs +++ b/src/Commands/Merq.Commands/ICommandBus.cs @@ -14,7 +14,7 @@ public interface ICommandBus : IFluentInterface /// /// The type of command to query. /// if the command has a registered handler. otherwise. - bool CanHandle() where TCommand : IExecutable; + bool CanHandle () where TCommand : IExecutable; /// /// Determines whether the given command has a registered handler. @@ -29,7 +29,7 @@ public interface ICommandBus : IFluentInterface /// /// The command parameters for the query. /// if the command can be executed. otherwise. - bool CanExecute (IExecutable command); + bool CanExecute (TCommand command) where TCommand : IExecutable; /// /// Executes the given synchronous command. @@ -43,7 +43,7 @@ public interface ICommandBus : IFluentInterface /// The return type of the command execution. /// The command parameters for the execution. /// The result of executing the command. - TResult Execute(ICommand command); + TResult Execute (ICommand command); /// /// Executes the given asynchronous command. @@ -59,6 +59,6 @@ public interface ICommandBus : IFluentInterface /// The command parameters for the execution. /// Cancellation token to cancel command execution. /// The result of executing the command. - Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation); + Task ExecuteAsync (IAsyncCommand command, CancellationToken cancellation); } }