Pipeline Behaviors¶
MediatR pipeline behaviors handle cross-cutting concerns before/after handlers.
Overview¶
flowchart TB
A[Request] --> B[ValidationBehavior]
B --> C[UserIdBehavior]
C --> D[GroupIdBehavior]
D --> E[HoneypotBehavior]
E --> F[CaptchaBehavior]
F --> G[BlockedDomainBehavior]
G --> H[CachingBehavior]
H --> I[Handler]
I --> J[CacheInvalidationBehavior]
J --> K[Response] Marker Interfaces¶
| Interface | Purpose | Behavior |
|---|---|---|
IRequestWithUserId | Inject current user ID | UserIdBehavior |
IRequestWithGroupId | Inject current group ID | GroupIdBehavior |
ICacheable | Cache response | CachingBehavior |
ICacheInvalidator | Invalidate cache keys | CacheInvalidationBehavior |
IRequestWithHoneypot | Bot protection | HoneypotBehavior |
IRequestWithCaptcha | CAPTCHA validation | CaptchaBehavior |
Honeypot Protection¶
Hidden field that bots fill but humans don't.
public interface IRequestWithHoneypot
{
string? Honeypot { get; }
}
public sealed record RegisterCommand(RegisterRequest Request)
: IRequest<Unit>, IRequestWithHoneypot
{
public string? Honeypot => Request.Honeypot;
}
Behavior rejects requests with filled honeypot:
CAPTCHA Validation¶
Cloudflare Turnstile validation for public endpoints.
public interface IRequestWithCaptcha
{
string? CaptchaToken { get; }
}
public sealed record RegisterCommand(RegisterRequest Request)
: IRequest<Unit>, IRequestWithCaptcha
{
public string? CaptchaToken => Request.CaptchaToken;
}
Behavior validates token with Turnstile API:
var isValid = await turnstileService.ValidateAsync(request.CaptchaToken);
if (!isValid)
{
throw new ValidationException("CAPTCHA validation failed");
}
Fail-closed: Invalid/missing tokens are rejected.
Blocked Email Domains¶
Prevents registration with disposable email domains.
public class BlockedEmailDomainBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(TRequest request, ...)
{
if (request is IRequestWithEmail emailRequest)
{
var domain = emailRequest.Email.Split('@').Last();
if (blockedDomains.Contains(domain))
{
throw new ValidationException("Email domain not allowed");
}
}
return await next();
}
}
Blocked domains configured via Security__Account__BlockedDomains.
Behavior Registration¶
Behaviors registered in order:
services.AddMediatR(cfg =>
{
cfg.AddBehavior<ValidationBehavior>();
cfg.AddBehavior<UserIdBehavior>();
cfg.AddBehavior<GroupIdBehavior>();
cfg.AddBehavior<HoneypotBehavior>();
cfg.AddBehavior<CaptchaBehavior>();
cfg.AddBehavior<BlockedEmailDomainBehavior>();
cfg.AddBehavior<CachingBehavior>();
cfg.AddBehavior<CacheInvalidationBehavior>();
});
Creating a Behavior¶
public class MyBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
// Before handler
var response = await next();
// After handler
return response;
}
}
Related¶
- CQRS — Command/Query pattern
- Validation — Request validation
- Caching — Response caching