-
Couldn't load subscription status.
- Fork 12
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.
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>.
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; }
}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;
}
}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<,>));
});
});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
});-
Generic CRUD Operations: Create a standard set of generic commands and queries (
CreateEntityCommand<T>,GetEntityByIdQuery<T>,UpdateEntityCommand<T>,DeleteEntityCommand<T>) for all your entities. -
Logging and Auditing: A generic
LogActivityCommand<TPayload>can be used to log different types of user activities with strongly-typed payloads. -
Data Synchronization: A generic
SyncDataCommand<TSource, TDestination>can handle data synchronization logic between different systems for various data types.
-
Register Open Generics: Always register the open generic type (e.g.,
MyHandler<,>) in the module builder. LiteBus handles the rest. -
Use Constraints: Apply generic constraints (
where T : ...) to your messages and handlers to ensure type safety and provide better IntelliSense. -
Combine with Generic Repositories: This pattern works exceptionally well with a generic repository pattern (
IRepository<TEntity>), as shown in the example.