Skip to content

Generic Messages and Handlers

A. Shafie edited this page Sep 26, 2025 · 1 revision

Generic Messages and Handlers

LiteBus provides full support for generic messages and handlers, allowing you to create reusable components for operations that share a common structure but operate on different data types. This is particularly useful for implementing patterns like a generic repository or CRUD operations.

What are Generic Messages and Handlers?

Generic messages and handlers use type parameters to define flexible contracts and logic. Instead of creating separate commands like CreateProductCommand and CreateUserCommand, you can create a single CreateEntityCommand<TEntity>.

How to Implement

1. Define a Generic Message

Create a command, query, or event with one or more generic type parameters.

/// <summary>
/// A generic command for creating any entity, returning the entity's ID.
/// </summary>
public sealed class CreateEntityCommand<TEntity, TKey> : ICommand<TKey>
    where TEntity : IEntity<TKey>
{
    public required TEntity EntityData { get; init; }
}

2. Implement a Generic Handler

The handler must also be generic, with type parameters that match the message it handles.

/// <summary>
/// A generic handler for creating any entity.
/// </summary>
public sealed class CreateEntityCommandHandler<TEntity, TKey>
    : ICommandHandler<CreateEntityCommand<TEntity, TKey>, TKey>
    where TEntity : class, IEntity<TKey>
{
    private readonly IRepository<TEntity, TKey> _repository;

    public CreateEntityCommandHandler(IRepository<TEntity, TKey> repository)
    {
        _repository = repository;
    }

    public async Task<TKey> HandleAsync(CreateEntityCommand<TEntity, TKey> command, CancellationToken cancellationToken = default)
    {
        await _repository.AddAsync(command.EntityData, cancellationToken);
        return command.EntityData.Id;
    }
}

3. Register the Generic Handler

When registering a generic handler with the dependency injection container, you must register its open generic type definition.

// In Program.cs
builder.Services.AddLiteBus(liteBus =>
{
    liteBus.AddCommandModule(module =>
    {
        // Register the open generic type.
        module.Register(typeof(CreateEntityCommandHandler<,>));
    });
});

4. Use the Generic Message

When you send a generic message, you provide the concrete types. LiteBus and the DI container will automatically resolve and instantiate the correct closed generic handler (e.g., CreateEntityCommandHandler<Product, Guid>).

// In a service or controller

// Create a new Product
var newProduct = new Product { Name = "Laptop", Price = 1200 };
var productId = await _commandMediator.SendAsync(new CreateEntityCommand<Product, Guid>
{
    EntityData = newProduct
});

// Create a new User
var newUser = new User { Username = "testuser" };
var userId = await _commandMediator.SendAsync(new CreateEntityCommand<User, int>
{
    EntityData = newUser
});

Use Cases

  1. Generic CRUD Operations: Create a standard set of generic commands and queries (CreateEntityCommand<T>, GetEntityByIdQuery<T>, UpdateEntityCommand<T>, DeleteEntityCommand<T>) for all your entities.
  2. Logging and Auditing: A generic LogActivityCommand<TPayload> can be used to log different types of user activities with strongly-typed payloads.
  3. Data Synchronization: A generic SyncDataCommand<TSource, TDestination> can handle data synchronization logic between different systems for various data types.

Best Practices

  1. Register Open Generics: Always register the open generic type (e.g., MyHandler<,>) in the module builder. LiteBus handles the rest.
  2. Use Constraints: Apply generic constraints (where T : ...) to your messages and handlers to ensure type safety and provide better IntelliSense.
  3. Combine with Generic Repositories: This pattern works exceptionally well with a generic repository pattern (IRepository<TEntity>), as shown in the example.
Clone this wiki locally