Skip to content
This repository was archived by the owner on Jul 2, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 52 additions & 7 deletions src/Commands/Merq.Commands.Tests/CommandBusSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using Moq;
using Xunit;

Expand Down Expand Up @@ -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<Result> (command);
var result = bus.Execute (command);

Assert.Same (expected, result);
}
Expand Down Expand Up @@ -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<ArgumentNullException> (() => new CommandBus (default(IEnumerable<ICommandHandler>)));
Assert.Throws<ArgumentNullException> (() => new CommandBus (default (IEnumerable<ICommandHandler>)));
}

[Fact]
public void when_can_handle_with_null_command_then_throws ()
{
Assert.Throws<ArgumentNullException> (() => new CommandBus ().CanHandle(null));
Assert.Throws<ArgumentNullException> (() => new CommandBus ().CanHandle (null));
}

[Fact]
public void when_can_execute_with_null_command_then_throws ()
{
Assert.Throws<ArgumentNullException> (() => new CommandBus ().CanExecute (null));
Assert.Throws<ArgumentNullException> (() => new CommandBus ().CanExecute<Command> (null));
}

[Fact]
public void when_execute_with_null_command_then_throws ()
{
Assert.Throws<ArgumentNullException> (() => new CommandBus ().Execute (default(Command)));
Assert.Throws<ArgumentNullException> (() => new CommandBus ().Execute (default (Command)));
}

[Fact]
public void when_execute_result_with_null_command_then_throws ()
{
Assert.Throws<ArgumentNullException> (() => new CommandBus ().Execute (default(CommandWithResult)));
Assert.Throws<ArgumentNullException> (() => new CommandBus ().Execute (default (CommandWithResult)));
}

[Fact]
public async Task when_executeasync_with_null_command_then_throws ()
{
await Assert.ThrowsAsync<ArgumentNullException> (() => new CommandBus ().ExecuteAsync (default(AsyncCommand), CancellationToken.None));
await Assert.ThrowsAsync<ArgumentNullException> (() => new CommandBus ().ExecuteAsync (default (AsyncCommand), CancellationToken.None));
}

[Fact]
Expand All @@ -223,6 +224,28 @@ public async Task when_executeasync_result_with_null_command_then_throws ()
await Assert.ThrowsAsync<ArgumentNullException> (() => 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<ICommandHandler<Command>>();
var command = new Command();
var bus = new CommandBus(handler.Object);

bus.Execute ((ICommand)command);

handler.Verify (x => x.Execute (command));
}

public class AsyncCommand : IAsyncCommand { }

Expand All @@ -232,6 +255,28 @@ public class Command : ICommand { }

public class CommandWithResult : ICommand<Result> { }

public class CommandWithResults : ICommand<IEnumerable<Result>> { }

public class Result { }

class NonPublicCommandHandlerWithResults : ICommandHandler<CommandWithResults, IEnumerable<Result>>
{
Result result;

public NonPublicCommandHandlerWithResults (Result result)
{
this.result = result;
}

bool ICanExecute<CommandWithResults>.CanExecute (CommandWithResults command)
{
return true;
}

IEnumerable<Result> ICommandHandler<CommandWithResults, IEnumerable<Result>>.Execute (CommandWithResults command)
{
yield return result;
}
}
}
}
107 changes: 94 additions & 13 deletions src/Commands/Merq.Commands/CommandBus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ public virtual bool CanHandle (IExecutable command)
/// </summary>
/// <param name="command">The command parameters for the query.</param>
/// <returns><see langword="true"/> if the command can be executed. <see langword="false"/> otherwise.</returns>
public virtual bool CanExecute (IExecutable command)
public virtual bool CanExecute<TCommand> (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<TCommand>)handler).CanExecute (command);
}

/// <summary>
Expand All @@ -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 });
}

/// <summary>
Expand All @@ -91,9 +92,10 @@ public virtual TResult Execute<TResult> (ICommand<TResult> 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 });
}

/// <summary>
Expand All @@ -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 });
}

/// <summary>
Expand All @@ -121,8 +124,66 @@ public virtual Task<TResult> ExecuteAsync<TResult> (IAsyncCommand<TResult> comma
{
if (command == null) throw new ArgumentNullException (nameof (command));

return (Task<TResult>)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> (TCommand command) where TCommand : ICommand
{
if (command == null) throw new ArgumentNullException (nameof (command));

var handler = GetCommandHandler(command);

handler.Execute (command);
}

TResult ExecuteCommandWithResult<TCommand, TResult> (TCommand command) where TCommand : ICommand<TResult>
{
if (command == null) throw new ArgumentNullException (nameof (command));

var handler = GetCommandHandler <TCommand, TResult>(command);

return handler.Execute (command);
}

Task ExecuteCommandAsync<TCommand> (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<TResult> ExecuteCommandWithResultAsync<TCommand, TResult> (TCommand command, CancellationToken cancellation) where TCommand : IAsyncCommand<TResult>
{
if (command == null) throw new ArgumentNullException (nameof (command));

var handler = GetAsyncCommandHandler <TCommand, TResult> (command);

return handler.ExecuteAsync ((dynamic)command, cancellation);
}

Expand Down Expand Up @@ -167,7 +228,7 @@ static string ProcessErrors (List<ICommandHandler> nonGenericHandlers, List<Tupl
.Select (handler => 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)) {
Expand All @@ -177,6 +238,26 @@ dynamic GetCommandHandler (IExecutable command)
return handler;
}

ICommandHandler<TCommand> GetCommandHandler<TCommand> (TCommand command) where TCommand : ICommand
{
return (ICommandHandler<TCommand>)GetCommandHandler ((IExecutable)command);
}

ICommandHandler<TCommand, TResult> GetCommandHandler<TCommand, TResult> (IExecutable command) where TCommand : ICommand<TResult>
{
return (ICommandHandler<TCommand, TResult>)GetCommandHandler (command);
}

IAsyncCommandHandler<TCommand> GetAsyncCommandHandler<TCommand> (TCommand command) where TCommand : IAsyncCommand
{
return (IAsyncCommandHandler<TCommand>)GetCommandHandler ((IExecutable)command);
}

IAsyncCommandHandler<TCommand, TResult> GetAsyncCommandHandler<TCommand, TResult> (IExecutable command) where TCommand : IAsyncCommand<TResult>
{
return (IAsyncCommandHandler<TCommand, TResult>)GetCommandHandler (command);
}

static Type GetCommandType (Type type)
{
return type.GetTypeInfo ().ImplementedInterfaces
Expand Down
8 changes: 4 additions & 4 deletions src/Commands/Merq.Commands/ICommandBus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface ICommandBus : IFluentInterface
/// </summary>
/// <typeparam name="TCommand">The type of command to query.</typeparam>
/// <returns><see langword="true"/> if the command has a registered handler. <see langword="false"/> otherwise.</returns>
bool CanHandle<TCommand>() where TCommand : IExecutable;
bool CanHandle<TCommand> () where TCommand : IExecutable;

/// <summary>
/// Determines whether the given command has a registered handler.
Expand All @@ -29,7 +29,7 @@ public interface ICommandBus : IFluentInterface
/// </summary>
/// <param name="command">The command parameters for the query.</param>
/// <returns><see langword="true"/> if the command can be executed. <see langword="false"/> otherwise.</returns>
bool CanExecute (IExecutable command);
bool CanExecute<TCommand> (TCommand command) where TCommand : IExecutable;

/// <summary>
/// Executes the given synchronous command.
Expand All @@ -43,7 +43,7 @@ public interface ICommandBus : IFluentInterface
/// <typeparam name="TResult">The return type of the command execution.</typeparam>
/// <param name="command">The command parameters for the execution.</param>
/// <returns>The result of executing the command.</returns>
TResult Execute<TResult>(ICommand<TResult> command);
TResult Execute<TResult> (ICommand<TResult> command);

/// <summary>
/// Executes the given asynchronous command.
Expand All @@ -59,6 +59,6 @@ public interface ICommandBus : IFluentInterface
/// <param name="command">The command parameters for the execution.</param>
/// <param name="cancellation">Cancellation token to cancel command execution.</param>
/// <returns>The result of executing the command.</returns>
Task<TResult> ExecuteAsync<TResult>(IAsyncCommand<TResult> command, CancellationToken cancellation);
Task<TResult> ExecuteAsync<TResult> (IAsyncCommand<TResult> command, CancellationToken cancellation);
}
}