MediatR provides several interfaces to handle cross-cutting concerns in a clean and modular way. These are especially useful in Blazor and other .NET applications.
🧩 Common MediatR Pipeline Interfaces
Interface | When it Runs | Typical Use Case |
---|---|---|
IRequestPreProcessor<TRequest> | Before handler | Validation, logging, setup |
IRequestPostProcessor<TRequest, TResponse> | After handler | Auditing, response modification |
IPipelineBehavior<TRequest, TResponse> | Before & after | Performance, error handling |
INotificationHandler<TNotification> | On event/notification | Publish/subscribe events |
📌 Interface Descriptions
🔹 IRequestPreProcessor<TRequest>
-
Runs before the request handler.
-
Common for validation, logging, setting context.
-
❌ No access to the response.
Example:
public class LoggingPreProcessor<TRequest> : IRequestPreProcessor<TRequest>
{
public Task Process(TRequest request, CancellationToken cancellationToken)
{
Console.WriteLine($"[PreProcessor] Handling request of type {typeof(TRequest).Name}");
return Task.CompletedTask;
}
}
🔹 IRequestPostProcessor<TRequest, TResponse>
-
Runs after the request handler.
-
Common for auditing, response logging, enrichment.
-
✅ Has access to both request and response.
Example:
public class ResponseLoggerPostProcessor<TRequest, TResponse> : IRequestPostProcessor<TRequest, TResponse>
{
public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken)
{
Console.WriteLine($"[PostProcessor] Response for {typeof(TRequest).Name}: {response}");
return Task.CompletedTask;
}
}
🔹 IPipelineBehavior<TRequest, TResponse>
-
Wraps around the handler – runs before and after.
-
Used for performance timing, retries, transactions, etc.
-
Can short-circuit execution.
Example:
public class TimingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var stopwatch = Stopwatch.StartNew();
Console.WriteLine($"[Behavior] Starting request: {typeof(TRequest).Name}");
var response = await next();
stopwatch.Stop();
Console.WriteLine($"[Behavior] Finished in {stopwatch.ElapsedMilliseconds} ms");
return response;
}
}
🔹 INotificationHandler<TNotification>
-
Listens for notifications published via
IMediator.Publish
. -
Multiple handlers can process the same event.
Example:
public class UserCreatedNotification : INotification
{
public string Username { get; set; }
}
public class WelcomeEmailHandler : INotificationHandler<UserCreatedNotification>
{
public Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken)
{
Console.WriteLine($"[Notification] Sending welcome email to {notification.Username}");
return Task.CompletedTask;
}
}
✅ Registering in DI (Dependency Injection)
Make sure to register MediatR and your pipeline behaviors in your Program.cs
(Blazor WASM/Server):
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(TimingBehavior<,>));
builder.Services.AddTransient(typeof(IRequestPreProcessor<>), typeof(LoggingPreProcessor<>));
builder.Services.AddTransient(typeof(IRequestPostProcessor<,>), typeof(ResponseLoggerPostProcessor<,>));
🧠 Summary
These interfaces help decouple concerns such as logging, validation, timing, and notifications, making your application logic cleaner and more maintainable—especially valuable in Blazor projects where separation of logic is key.
In a Blazor (or any modern .NET) application, the Pipeline with Behaviors is a way to add reusable logic that runs before or after your main business logic (like handling a request or command). This is often implemented using the MediatR library, which supports a “pipeline” of behaviors. Why use Pipeline Behaviors? • Separation of Concerns: Each behavior handles a specific cross-cutting concern (like logging, validation, authorization) without mixing it into your main business logic. • Reusability: You write the logic once and it applies to all relevant requests. • Consistency: Ensures things like validation or logging always happen in the same way for every request. What does each behavior do? • AuthorizationBehavior: Checks if the user is allowed to perform the action before it happens. • LoggingBehavior: Logs information about the request and/or response for debugging or auditing. • PerformanceBehavior: Measures how long the request takes, so you can spot slow operations. • UnhandledExceptionBehavior: Catches any unhandled errors and logs them, preventing the app from crashing unexpectedly. • ValidationBehavior: Checks if the request data is valid before processing it. Simple Analogy Think of it like an assembly line: • Each behavior is a station on the line. • Every request passes through each station (behavior) in order. • Each station can inspect, modify, or stop the request. Example Flow
- ValidationBehavior: Is the request data valid?
- AuthorizationBehavior: Is the user allowed?
- LoggingBehavior: Log the request.
- PerformanceBehavior: Start timing.
- Main Handler: Do the actual work.
- PerformanceBehavior: Stop timing.
- UnhandledExceptionBehavior: If anything fails, catch and log. This keeps your code clean, maintainable, and robust.