FlashMediator 1.0.5

dotnet add package FlashMediator --version 1.0.5
                    
NuGet\Install-Package FlashMediator -Version 1.0.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FlashMediator" Version="1.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FlashMediator" Version="1.0.5" />
                    
Directory.Packages.props
<PackageReference Include="FlashMediator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FlashMediator --version 1.0.5
                    
#r "nuget: FlashMediator, 1.0.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FlashMediator@1.0.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FlashMediator&version=1.0.5
                    
Install as a Cake Addin
#tool nuget:?package=FlashMediator&version=1.0.5
                    
Install as a Cake Tool

FlashMediator

NuGet Version NuGet Downloads License

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 IDistributedCache provider 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 AddFlashMediatorHybridCache only 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.5 135 8/2/2026
1.0.1 398 12/18/2025
1.0.0 147 12/12/2025

Query Cache Added