Commands and queries
The framework implements a number of facilities for working with commands, queries and for implementing CQRS.

Commands, queries

Commands and queries can be defined as regular POCO classes implementing ICommand or IQuery<T> (with T defining the query return type) interfaces. These interfaces are empty on their own and only define the contract of being processable by a command bus.
Because both IQuery and ICommand derive from a common ICommandBase ancestor, the framework considers queries to be simply a specific subtype of commands that happen to also return a value.
Example command:
1
public class CreateUserCommand : ICommand
2
{
3
public CreateUserCommand(string firstName, string lastName,
4
string emailAddress, string password)
5
{
6
FirstName = firstName;
7
LastName = lastName;
8
EmailAddress = emailAddress;
9
Password = password;
10
}
11
12
public string FirstName { get; }
13
public string LastName { get; }
14
public string EmailAddress { get; }
15
public string Password { get; }
16
}
Copied!
Example query:
1
public class GetAllUsersQuery : IQuery<List<UserDto>>
2
{
3
public GetAllUsersQuery(string filterFirstName, string filterLastName)
4
{
5
FilterFirstName = filterFirstName;
6
FilterLastName = filterLastName;
7
}
8
9
public string FilterFirstName { get; }
10
public string FilterLastName { get; }
11
}
Copied!
It is advisable to make the command/query classes immutable (as can be seen in the example) to prevent undesirable modifications as the object gets passed throughout the system.

Command/query handlers

Commands and queries can be handled by implementing ICommandHandler<TCommand> or IQueryHandler<TQuery, TResult> respectively. For every command or query type, there should be exactly one handler type registered in the dependency container, otherwise an exception will be thrown when trying to handle it.
1
public interface ICommandHandler<in TCommand>
2
where TCommand : ICommand
3
{
4
Task HandleAsync(T command, CancellationToken cancellationToken);
5
}
6
7
public interface ICommandHandler<in TCommand, TResult>
8
where TCommand: ICommand<TResult>
9
{
10
Task<TResult> HandleAsync(TCommand query, CancellationToken cancellationToken);
11
}
12
13
public interface IQueryHandler<TQuery, TResult>
14
: ICommandHandler<TQuery, TResult>
15
where TQuery : IQuery<TResult>
16
{
17
}
Copied!
By default, all command and query handlers get auto-discovered in all referenced assemblies and registered in task scope.

Command bus

To send a command or query to the system, one can use an ICommandBus which encapsulates most of the command processing details.
1
public interface ICommandBus
2
{
3
Task<TResult> SendAsync<TResult>(ICommand<TResult> command,
4
CancellationToken cancellationToken = default(CancellationToken));
5
Task SendAsync(ICommandBase command,
6
CancellationToken cancellationToken = default(CancellationToken));
7
}
Copied!
The command bus is designed to accept any command or query type an resolves the corresponding handlers during runtime. By default, the command handling also starts a new unit of work that is automatically committed at the end of the handling pipeline – to find more about this, see chapter describing the request life-cycle.

Command filters

Command filters provide a way for dealing with cross-cutting concerns when handling their execution. It is possible to define action that will get executed before invoking a command handler, after invoking it and after invoking it in case it results in an error exception.
1
public interface IPreCommandFilter<in T>
2
where T : ICommandBase
3
{
4
Task PreFilterAsync(T command);
5
}
6
7
public interface IPostCommandFilter<in T>
8
where T : ICommandBase
9
{
10
Task PostFilterAsync(T command, object result);
11
}
12
13
public interface IExceptionCommandFilter<in T>
14
where T : ICommandBase
15
{
16
Task FilterExceptionAsync(T command, Exception e);
17
}
Copied!
These filters get automatically invoked when registered in the dependency container for a specific command (base) type. The framework uses them to deal with concerns like authorization or automatic unit-of-work management, but it is also possible to define custom application’s own filters.
Last modified 1yr ago