Skip to content

1. Writing Commands

Alex McNair edited this page Jul 3, 2020 · 1 revision

Commands are requests to change the state of the system. They contain the information required to make the necessary changes. They are simple POCOs that should be immutable, and must implement the ICommand interface and use the CommandNameAttribute.

Command

This is an example of a command that represents the request to add a new user to the system. The CommandName attribute is used as a key to resolve the relevant types at runtime, and therefore to decouple the "Command Name" with the actual type name.

The command name should be unique within the scope of the running application.

[CommandName("Api/AddUser")]
public class AddUser : ICommand
{
    public AddUser(int id, string name)
    {
        this.Id = id;
        this.Name = name;
    }

    public int Id { get; }

    public string Name { get; }
}

Command Handler

Each command must have a handler. Simply implement the generic ICommandHandler<> interface. It has one method, HandleAsync, that is designed to be called dynamically by the framework, and passed an instance of the incoming command. The handler method is also passed a CommandMetadata object which provides information about the current command.

If you want to do additional validation in the handler, you can throw the CommandApi.Exceptions.InvalidCommandException. This will result in a 400 Bad Request being returned from the api.

public class AddUserHandler : ICommandHandler<AddUser>
{
    public Task HandleAsync(AddUser command, CommandMetadata metadata)
    {
        // handle the command
    }
}

Command Validator

As commands are requests to change the state of the system, they can be rejected. One reason that a command may be rejected is because it is invalid.

Therefore, a command can optionally have a validator. The pipeline will validate the command before the handler is executed.

The CommandValidator<> uses Fluent Validation.

public class AddUserValidator : CommandValidator<AddUser>
{
    public override void ConfigureRules()
    {
        this.RuleFor(x => x.Id)
            .GreaterThan(0)
            .WithMessage("Id must be greater than zero.");

        this.RuleFor(x => x.Name)
            .Must(x => !string.IsNullOrWhiteSpace(x))
            .WithMessage("Name must be provided.");
    }
}

Command Authorization Provider

A command can also optionally have a authorization provider. This is to provide any additional authorization logic specific to the scope of the data in the command.

By default, the command will be authorized before it is validated and handled.

* The order of validation and authorization can be changed when registering the pipeline.

public class AddUserAuth : ICommandAuthProvider<AddUser>
{
    public Task<bool> IsAuthorized(AddUser command, CommandMetadata metadata)
    {
      // authorization logic
    }
}

Clone this wiki locally