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