MediatR pipeline behaviors for TheTechLoop.HybridCache. Provides automatic convention-based caching and cache invalidation via ICacheable and ICacheInvalidatable marker interfaces. Install this package to add MediatR pipeline behavior support to your caching layer.
$ dotnet add package TheTechLoop.HybridCache.MediatRMediatR pipeline behaviors for TheTechLoop.HybridCache — automatic convention-based caching and cache invalidation for your CQRS microservices.
This package provides MediatR pipeline behaviors that integrate with TheTechLoop.HybridCache to deliver zero-boilerplate caching in CQRS architectures. Instead of writing cache logic in every query handler, simply mark your requests with a marker interface and let the pipeline handle the rest.
| Component | Purpose |
|---|---|
ICacheable | Marker interface for queries that should be automatically cached |
ICacheInvalidatable | Marker interface for commands that should invalidate cache after execution |
CachingBehavior<TRequest, TResponse> | Pipeline behavior that intercepts ICacheable queries and caches responses |
CacheInvalidationBehavior<TRequest, TResponse> | Pipeline behavior that invalidates cache entries after ICacheInvalidatable commands succeed |
AddTheTechLoopCacheBehaviors() | DI extension method to register both behaviors |
dotnet add package TheTechLoop.HybridCache.MediatR
Prerequisite: You must also have TheTechLoop.HybridCache installed and configured.
// Program.cs
using TheTechLoop.HybridCache.Extensions;
using TheTechLoop.HybridCache.MediatR.Extensions;
// Register core cache services (from TheTechLoop.HybridCache)
builder.Services.AddTheTechLoopCache(builder.Configuration);
// Register MediatR
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
// Register MediatR pipeline behaviors (from this package)
builder.Services.AddTheTechLoopCacheBehaviors();
ICacheableMark any MediatR query with ICacheable to enable automatic caching. The handler remains pure — no cache logic needed:
using TheTechLoop.HybridCache.MediatR.Abstractions;
// The query declares its cache behavior
public record GetDealershipByIdQuery(int Id) : IRequest<Dealership?>, ICacheable
{
public string CacheKey => $"Dealership:{Id}";
public TimeSpan CacheDuration => TimeSpan.FromMinutes(30);
}
// The handler has ZERO cache logic
public class GetDealershipByIdQueryHandler : IRequestHandler<GetDealershipByIdQuery, Dealership?>
{
private readonly IReadRepository<Dealership> _repository;
public async Task<Dealership?> Handle(GetDealershipByIdQuery request, CancellationToken ct)
{
return await _repository.Query
.FirstOrDefaultAsync(d => d.ID == request.Id, ct);
}
}
What happens:
CachingBehavior intercepts the request before the handler runs"company-svc:v1:Dealership:42"ICacheInvalidatableMark MediatR commands to automatically invalidate cache entries after successful execution:
using TheTechLoop.HybridCache.MediatR.Abstractions;
public record UpdateDealershipCommand(int Id, string Name) : IRequest<bool>, ICacheInvalidatable
{
// Exact keys to remove
public IReadOnlyList<string> CacheKeysToInvalidate =>
[$"Dealership:{Id}"];
// Prefix patterns — all matching keys are removed
public IReadOnlyList<string> CachePrefixesToInvalidate =>
["Dealership:Search", "Dealership:List"];
}
What happens:
CacheInvalidationBehavior runs after the handler succeedsController
→ MediatR.Send(GetDealershipByIdQuery)
→ CachingBehavior intercepts (ICacheable detected)
→ ICacheService.GetOrCreateAsync("company-svc:v1:Dealership:42")
→ [Cache Hit] → Return cached value (handler skipped)
→ [Cache Miss] → Execute handler → Cache result → Return
Controller
→ MediatR.Send(UpdateDealershipCommand)
→ Handler executes (DB write)
→ CacheInvalidationBehavior runs (ICacheInvalidatable detected)
→ ICacheService.RemoveAsync("company-svc:v1:Dealership:42")
→ ICacheService.RemoveByPrefixAsync("company-svc:v1:Dealership:Search")
→ ICacheInvalidationPublisher.PublishAsync(key) ← cross-service
→ ICacheInvalidationPublisher.PublishPrefixAsync(prefix) ← cross-service
public interface ICacheable
{
/// <summary>
/// Cache key for this request. Automatically prefixed with service name and version.
/// Example: "Dealership:42" → "company-svc:v1:Dealership:42"
/// </summary>
string CacheKey { get; }
/// <summary>
/// How long the cached response should live.
/// </summary>
TimeSpan CacheDuration { get; }
}
public interface ICacheInvalidatable
{
/// <summary>
/// Exact cache keys to remove after the command succeeds.
/// Example: ["Dealership:42"]
/// </summary>
IReadOnlyList<string> CacheKeysToInvalidate { get; }
/// <summary>
/// Cache key prefixes for pattern-based invalidation.
/// Example: ["Dealership:Search", "Dealership:List"]
/// </summary>
IReadOnlyList<string> CachePrefixesToInvalidate { get; }
}
// Registers both CachingBehavior and CacheInvalidationBehavior
services.AddTheTechLoopCacheBehaviors();
// Program.cs
using TheTechLoop.HybridCache.Extensions;
using TheTechLoop.HybridCache.MediatR.Extensions;
var builder = WebApplication.CreateBuilder(args);
// 1. Core cache services
builder.Services.AddTheTechLoopCache(builder.Configuration);
// 2. Optional: Multi-level caching (L1 Memory + L2 Redis)
builder.Services.AddTheTechLoopMultiLevelCache(builder.Configuration);
// 3. Optional: Cross-service cache invalidation via Pub/Sub
builder.Services.AddTheTechLoopCacheInvalidation(builder.Configuration);
// 4. MediatR
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
// 5. MediatR cache behaviors (this package)
builder.Services.AddTheTechLoopCacheBehaviors();
// 6. Optional: OpenTelemetry metrics
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddMeter("TheTechLoop.Cache"); // Core metrics
metrics.AddMeter("TheTechLoop.Cache.Effectiveness"); // Per-entity tracking
metrics.AddPrometheusExporter();
});
var app = builder.Build();
app.Run();
| Data Type | Key Pattern | TTL |
|---|---|---|
| Entity by ID | "Entity:{Id}" | 15–30 minutes |
| Search results | "Entity:Search:{Term}:{PageSize}" | 3–5 minutes |
| List / pagination | "Entity:List:{Page}:{Size}" | 3–5 minutes |
| Reference data | "Country:{Id}" | 6–10 hours |
MIT