FlashMediator 1.0.5
dotnet add package FlashMediator --version 1.0.5
NuGet\Install-Package FlashMediator -Version 1.0.5
<PackageReference Include="FlashMediator" Version="1.0.5" />
<PackageVersion Include="FlashMediator" Version="1.0.5" />
<PackageReference Include="FlashMediator" />
paket add FlashMediator --version 1.0.5
#r "nuget: FlashMediator, 1.0.5"
#:package FlashMediator@1.0.5
#addin nuget:?package=FlashMediator&version=1.0.5
#tool nuget:?package=FlashMediator&version=1.0.5
FlashMediator
FlashMediator is a lightweight, dependency-injection-friendly mediator library for building CQRS-style .NET applications.
It provides request/response dispatching, commands without responses, pipeline behaviors, automatic handler discovery, and optional two-level caching through Microsoft HybridCache.
Features
- Request/response messaging
- Commands without response values
- Automatic request handler registration
- Open generic pipeline behaviors
- Cancellation token propagation
- Reflection-cached request dispatching
- Optional HybridCache integration
- In-memory L1 caching by default
- Optional Redis or another
IDistributedCacheprovider as L2 - Tag-based cache invalidation
- No Redis requirement
- Native .NET dependency injection support
Requirements
FlashMediator targets:
- .NET 10.0 or later
- Microsoft.Extensions.DependencyInjection
Installation
Install FlashMediator from NuGet:
dotnet add package FlashMediator
Or with the NuGet Package Manager:
Install-Package FlashMediator
Registration
Register FlashMediator and specify the assemblies that contain your request handlers:
using FlashMediator;
builder.Services.AddFlashMediator(typeof(Program).Assembly);
Multiple assemblies can be registered:
builder.Services.AddFlashMediator(
typeof(Program).Assembly,
typeof(ApplicationAssemblyMarker).Assembly);
FlashMediator scans the supplied assemblies and registers implementations of:
IRequestHandler<TRequest>IRequestHandler<TRequest, TResponse>
Request with a Response
Define a request by implementing IRequest<TResponse>:
public sealed record GetProductQuery(Guid ProductId)
: IRequest<ProductDto>;
Create its handler:
public sealed class GetProductQueryHandler
: IRequestHandler<GetProductQuery, ProductDto>
{
private readonly IProductRepository _products;
public GetProductQueryHandler(IProductRepository products)
{
_products = products;
}
public async Task<ProductDto> Handle(
GetProductQuery request,
CancellationToken cancellationToken)
{
return await _products.GetByIdAsync(
request.ProductId,
cancellationToken);
}
}
Send the request through IMediator:
public sealed class ProductService
{
private readonly IMediator _mediator;
public ProductService(IMediator mediator)
{
_mediator = mediator;
}
public Task<ProductDto> GetProductAsync(
Guid productId,
CancellationToken cancellationToken)
{
return _mediator.Send(
new GetProductQuery(productId),
cancellationToken);
}
}
Command without a Response
Use IRequest for operations that do not return a response value:
public sealed record DeleteProductCommand(Guid ProductId)
: IRequest;
Create its handler:
public sealed class DeleteProductCommandHandler
: IRequestHandler<DeleteProductCommand>
{
private readonly IProductRepository _products;
public DeleteProductCommandHandler(IProductRepository products)
{
_products = products;
}
public async Task Handle(
DeleteProductCommand request,
CancellationToken cancellationToken)
{
await _products.DeleteAsync(
request.ProductId,
cancellationToken);
}
}
Send the command:
await mediator.Send(
new DeleteProductCommand(productId),
cancellationToken);
Pipeline Behaviors
Pipeline behaviors can run logic before and after request handlers.
Common use cases include:
- Logging
- Validation
- Authorization
- Transactions
- Performance monitoring
- Exception handling
- Caching
Create an open generic behavior:
public sealed class LoggingBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(
ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
_logger.LogInformation(
"Handling request {RequestType}",
typeof(TRequest).Name);
var response = await next();
_logger.LogInformation(
"Handled request {RequestType}",
typeof(TRequest).Name);
return response;
}
}
Register the behavior:
builder.Services.AddPipelineBehavior(
typeof(LoggingBehavior<,>));
Behaviors execute in registration order before the handler and unwind in reverse order after the handler completes.
HybridCache Integration
FlashMediator can automatically cache requests that implement ICacheableQuery.
Enable HybridCache support:
builder.Services
.AddFlashMediator(typeof(Program).Assembly)
.AddFlashMediatorHybridCache();
Without a distributed cache provider, HybridCache uses the application's in-memory cache as L1.
Request
↓
L1 Memory Cache
↓ cache miss
Request Handler
↓
Store response in L1
Cacheable Queries
Implement both IRequest<TResponse> and ICacheableQuery:
public sealed record GetProductByIdQuery(Guid ProductId)
: IRequest<ProductDto>, ICacheableQuery
{
public string CacheKey => $"products:{ProductId}";
public TimeSpan? Expiration =>
TimeSpan.FromMinutes(10);
public TimeSpan? LocalCacheExpiration =>
TimeSpan.FromMinutes(1);
public IReadOnlyCollection<string> CacheTags =>
["products", $"product:{ProductId}"];
}
The cache configuration properties are:
| Property | Description |
|---|---|
CacheKey |
Unique key used to store and retrieve the response |
Expiration |
Overall cache lifetime, including the distributed cache |
LocalCacheExpiration |
Lifetime of the entry in the local in-memory cache |
CacheTags |
Tags associated with the cached response |
On a cache hit, the handler is not executed.
On a cache miss, FlashMediator executes the handler and stores its response through HybridCache.
Redis as L2 Cache
HybridCache automatically uses a registered IDistributedCache implementation as its secondary cache.
Install the Redis provider:
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
Register Redis:
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration =
builder.Configuration.GetConnectionString("Redis");
});
Then enable FlashMediator caching:
builder.Services
.AddFlashMediator(typeof(Program).Assembly)
.AddFlashMediatorHybridCache();
The resulting cache hierarchy is:
Request
↓
L1: Local Memory Cache
↓ cache miss
L2: Redis
↓ cache miss
Request Handler
↓
Store response in L1 and L2
L1 belongs to the current application instance. L2 is shared by all application instances connected to the same distributed cache.
Tag-Based Cache Invalidation
Commands can invalidate cached queries by implementing ICacheInvalidator.
public sealed record UpdateProductCommand(
Guid ProductId,
string Name)
: IRequest, ICacheInvalidator
{
public IReadOnlyCollection<string> CacheTags =>
["products", $"product:{ProductId}"];
}
Create the command handler normally:
public sealed class UpdateProductCommandHandler
: IRequestHandler<UpdateProductCommand>
{
private readonly IProductRepository _products;
public UpdateProductCommandHandler(
IProductRepository products)
{
_products = products;
}
public async Task Handle(
UpdateProductCommand request,
CancellationToken cancellationToken)
{
await _products.UpdateAsync(
request.ProductId,
request.Name,
cancellationToken);
}
}
After the handler completes successfully, FlashMediator invalidates entries associated with the specified tags using HybridCache.RemoveByTagAsync.
UpdateProductCommand
↓
Command Handler
↓ success
Invalidate "products"
Invalidate "product:{id}"
If the handler throws an exception, cache invalidation is not performed.
A create command usually invalidates the collection tag:
public sealed record CreateProductCommand(string Name)
: IRequest<Guid>, ICacheInvalidator
{
public IReadOnlyCollection<string> CacheTags =>
["products"];
}
An update or delete command can invalidate both collection and entity tags:
public IReadOnlyCollection<string> CacheTags =>
["products", $"product:{ProductId}"];
Cache Key Recommendations
Cache keys should include every value that can change the result.
For example:
public string CacheKey =>
$"tenant:{TenantId}:products:{ProductId}";
Consider including:
- Tenant or organization identifier
- User identifier for user-specific data
- Entity identifier
- Page number and page size
- Filters and sorting parameters
- Language or culture
Avoid using the same cache key for results with different authorization or tenant scopes.
Public API Overview
| Type | Purpose |
|---|---|
IMediator |
Sends requests to their registered handlers |
IRequest<TResponse> |
Defines a request that returns a response |
IRequest |
Defines a request without a response |
IRequestHandler<TRequest, TResponse> |
Handles a request with a response |
IRequestHandler<TRequest> |
Handles a request without a response |
IPipelineBehavior<TRequest, TResponse> |
Adds cross-cutting behavior around handlers |
RequestHandlerDelegate<TResponse> |
Represents the next step in the pipeline |
ICacheableQuery |
Marks a request as cacheable |
ICacheInvalidator |
Marks a request as a cache invalidator |
Complete Registration Example
using FlashMediator;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration =
builder.Configuration.GetConnectionString("Redis");
});
builder.Services
.AddFlashMediator(typeof(Program).Assembly)
.AddPipelineBehavior(typeof(LoggingBehavior<,>))
.AddFlashMediatorHybridCache();
var app = builder.Build();
app.Run();
Redis registration is optional. Remove AddStackExchangeRedisCache to use local in-memory caching only.
Notes
- Register
AddFlashMediatorHybridCacheonly when query caching is required. - Cache invalidation occurs after a successful handler execution.
- Cache tags should represent the resources affected by a command.
- Keep local cache expiration shorter when running multiple application instances.
- Cached response types should be serializable when using a distributed cache.
- Cached objects should preferably be immutable.
- Applications must remain correct when cached data expires or is unavailable.
Repository
Source code and issue tracking are available on GitHub:
https://github.com/emreucbudak/FlashMediator
License
FlashMediator is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Caching.Hybrid (>= 10.1.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Query Cache Added