FastMediator 1.0.0
dotnet add package FastMediator --version 1.0.0
NuGet\Install-Package FastMediator -Version 1.0.0
<PackageReference Include="FastMediator" Version="1.0.0" />
<PackageVersion Include="FastMediator" Version="1.0.0" />
<PackageReference Include="FastMediator" />
paket add FastMediator --version 1.0.0
#r "nuget: FastMediator, 1.0.0"
#:package FastMediator@1.0.0
#addin nuget:?package=FastMediator&version=1.0.0
#tool nuget:?package=FastMediator&version=1.0.0
FastMediator
FastMediator is a lightweight and high-performance implementation of the Mediator pattern for .NET, optimized for performance and ease of use. Designed for applications that require high throughput with minimal overhead, it allows decoupling application components by implementing CQRS (Command Query Responsibility Segregation) in a simple and elegant way.
Features
- 🚀 High Performance: Uses compiled expressions and delegate caching for optimal performance
- 🧩 CQRS Support: Clear separation between commands (requests that modify state) and queries (requests that return data)
- 🔄 Behavior Pipeline: Ability to intercept requests with configurable behaviors such as validation, logging, and performance measurement
- 📢 Notification System: Support for the publish/subscribe pattern with notifications to multiple handlers
- 🔍 Integrated Diagnostics: Detailed logging functionality and performance measurement to simplify debugging and optimization
- ✅ Integrated Validation: Validation of incoming requests before processing
- 🧰 Simple Configuration: Seamless integration with Microsoft.Extensions.DependencyInjection
- 🔄 Full Asynchronous Support: Fully asynchronous API and pipeline with CancellationToken support
- ⚡ Flexible Registration Modes: Startup, LazyLoading, or Hybrid to optimize performance and resource consumption
- 🔄 Synchronous/Asynchronous Interoperability: Smooth conversion between synchronous and asynchronous requests
Installation
dotnet add package FastMediator
Configuration
Configure FastMediator in your IoC container with different registration modes:
services.AddCustomMediator(scan => scan.FromAssemblyOf<Program>(), options =>
{
// Enable optional behaviors
options.EnableDiagnostics = true; // Enable diagnostic behaviors
options.EnableTiming = true; // Enable timing measurement
options.EnableDetailedLogging = true; // Enable detailed logging
options.LoggerFactory = loggerFactory; // Optional: factory for loggers
// Choose handler registration mode
options.RegistrationMode = HandlerRegistrationMode.Startup; // All at startup (default)
// OR
options.RegistrationMode = HandlerRegistrationMode.LazyLoading; // On first use
// OR
options.UseHybridMode() // Hybrid mode with fluent API
.WithWarmup<PingRequest>() // Preload specific handlers
.WithWarmup<AnotherRequest>(); // Add more types to preload
});
Basic Usage
1. Define a Request and its Handler
// Synchronous request
public class Ping : IRequest<string>
{
public string Message { get; }
public Ping(string message)
{
Message = message;
}
}
// Synchronous handler
public class PingHandler : IRequestHandler<Ping, string>
{
public string Handle(Ping request)
{
return $"Response to: {request.Message}";
}
}
// Asynchronous request
public class AsyncPing : IAsyncRequest<string>
{
public string Message { get; }
public AsyncPing(string message)
{
Message = message;
}
}
// Asynchronous handler
public class AsyncPingHandler : IAsyncRequestHandler<AsyncPing, string>
{
public async Task<string> HandleAsync(AsyncPing request, CancellationToken cancellationToken = default)
{
await Task.Delay(100, cancellationToken);
return $"Asynchronous response to: {request.Message}";
}
}
2. Send the Request
// Inject the dispatcher
public class MyService
{
private readonly Dispatcher _mediator;
public MyService(Dispatcher mediator)
{
_mediator = mediator;
}
// Synchronous sending
public void ProcessMessage(string message)
{
string response = _mediator.Send(new Ping(message));
Console.WriteLine(response);
}
// Asynchronous sending
public async Task ProcessMessageAsync(string message, CancellationToken cancellationToken = default)
{
string response = await _mediator.SendAsync(new AsyncPing(message), cancellationToken);
Console.WriteLine(response);
}
// Synchronous sending using asynchronous API
public async Task ProcessSyncMessageAsAsync(string message)
{
string response = await _mediator.SendAsAsync<Ping, string>(new Ping(message));
Console.WriteLine(response);
}
// Asynchronous sending using synchronous API
public void ProcessAsyncMessageSync(string message)
{
string response = _mediator.SendSync<AsyncPing, string>(new AsyncPing(message));
Console.WriteLine(response);
}
}
Notifications
Notifications allow publishing events to multiple handlers (synchronous and asynchronous).
1. Define a Notification and its Handlers
// Synchronous notification
public class SomethingHappened : INotification
{
public string Message { get; set; }
}
// Synchronous handler
public class SomethingHappenedHandler : INotificationHandler<SomethingHappened>
{
public void Handle(SomethingHappened notification)
{
Console.WriteLine($"Event handled: {notification.Message}");
}
}
// Asynchronous notification
public class AsyncSomethingHappened : IAsyncNotification
{
public string Message { get; set; }
}
// Asynchronous handler
public class AsyncSomethingHappenedHandler : IAsyncNotificationHandler<AsyncSomethingHappened>
{
public async Task HandleAsync(AsyncSomethingHappened notification, CancellationToken cancellationToken = default)
{
await Task.Delay(100, cancellationToken);
Console.WriteLine($"Asynchronous event handled: {notification.Message}");
}
}
2. Publish the Notification
// Synchronous publishing
_mediator.Publish(new SomethingHappened { Message = "An important event has occurred!" });
// Asynchronous publishing (executes all handlers in parallel)
await _mediator.PublishAsync(new AsyncSomethingHappened { Message = "An asynchronous event has occurred!" });
// Sequential asynchronous publishing (one handler at a time)
await _mediator.PublishSequentialAsync(new AsyncSomethingHappened { Message = "A sequential event has occurred!" });
Behavior Pipeline
Behaviors allow intercepting and manipulating requests before they reach the handler.
Included Behaviors
FastMediator includes several ready-to-use behaviors for synchronous and asynchronous requests:
ValidationBehavior
/ValidationBehaviorAsync
: Validates requests before processingLoggingBehavior
/LoggingBehaviorAsync
: Logs details of requests and responsesTimingBehavior
/TimingBehaviorAsync
: Measures the processing time of requestsDiagnosticBehavior
/DiagnosticBehaviorAsync
: Provides information about the behavior pipeline
Creating a Custom Behavior
// Synchronous behavior
public class MyCustomBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>, IOrderedPipelineBehavior
where TRequest : IRequest<TResponse>
{
// Execution priority (lower = higher priority)
public int Order => 100;
public TResponse Handle(TRequest request, Func<TRequest, TResponse> next)
{
// Pre-processing logic
Console.WriteLine($"Pre-processing for {typeof(TRequest).Name}");
// Call the next handler in the pipeline
var response = next(request);
// Post-processing logic
Console.WriteLine($"Post-processing for {typeof(TRequest).Name}");
return response;
}
}
// Asynchronous behavior
public class MyCustomAsyncBehavior<TRequest, TResponse> : IPipelineBehaviorAsync<TRequest, TResponse>, IOrderedPipelineBehavior
where TRequest : IAsyncRequest<TResponse>
{
// Execution priority (lower = higher priority)
public int Order => 100;
public async Task<TResponse> HandleAsync(TRequest request, Func<TRequest, CancellationToken, Task<TResponse>> next, CancellationToken cancellationToken = default)
{
// Pre-processing logic
Console.WriteLine($"Asynchronous pre-processing for {typeof(TRequest).Name}");
// Call the next handler in the pipeline
var response = await next(request, cancellationToken);
// Post-processing logic
Console.WriteLine($"Asynchronous post-processing for {typeof(TRequest).Name}");
return response;
}
}
// Registration in the container
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(MyCustomBehavior<,>));
services.AddTransient(typeof(IPipelineBehaviorAsync<,>), typeof(MyCustomAsyncBehavior<,>));
Validation
FastMediator includes an integrated validation system, for both synchronous and asynchronous requests.
1. Create a Validator
public class CreateUserValidator : AbstractValidator<CreateUserCommand>
{
protected override void ValidateInternal(CreateUserCommand request, ValidationResult result)
{
if (string.IsNullOrEmpty(request.Username))
result.AddError(nameof(request.Username), "Username is required");
if (request.Password?.Length < 8)
result.AddError(nameof(request.Password), "Password must be at least 8 characters");
}
}
2. Register it in the IoC Container
The validator is automatically registered if you use the assembly scanning method.
Registration Modes
FastMediator supports different handler registration modes to optimize performance and resource usage:
// 1. Startup Mode (default) - All handlers are registered at startup
options.RegistrationMode = HandlerRegistrationMode.Startup;
// 2. LazyLoading Mode - Handlers are registered on first use
options.RegistrationMode = HandlerRegistrationMode.LazyLoading;
// 3. Hybrid Mode - Preloads only specified handlers, others on first use
options.UseHybridMode()
.WithWarmup<PingRequest>()
.WithWarmup<AnotherRequest>();
DelegateCache and Performance
FastMediator uses a delegate caching system to maximize performance:
// Get cache statistics
var stats = mediator.GetRequestHandlerCacheStats();
Console.WriteLine($"Cache hits: {stats.Hits}, misses: {stats.Misses}");
// Cache size
Console.WriteLine($"Cache request handlers: {mediator.RequestHandlerCacheSize}");
Console.WriteLine($"Cache notification handlers: {mediator.NotificationHandlerCacheSize}");
Console.WriteLine($"Cache async request handlers: {mediator.AsyncRequestHandlerCacheSize}");
Console.WriteLine($"Cache async notification handlers: {mediator.AsyncNotificationHandlerCacheSize}");
Synchronous/Asynchronous Interoperability
FastMediator offers extension methods to convert synchronous calls to asynchronous and vice versa:
// From synchronous to asynchronous
await mediator.SendAsAsync<PingRequest, string>(new PingRequest("Test"));
await mediator.PublishAsAsync(new SomethingHappened { Message = "Test" });
// From asynchronous to synchronous
string result = mediator.SendSync<AsyncPingRequest, string>(new AsyncPingRequest("Test"));
mediator.PublishSync(new AsyncSomethingHappened { Message = "Test" });
Advanced Scenarios
CQRS with Different Request Types
// Synchronous query (returns data without state modification)
public class GetUserQuery : IRequest<UserDto> { public int UserId { get; set; } }
// Synchronous command (modifies state)
public class CreateUserCommand : IRequest<int>
{
public string Username { get; set; }
public string Email { get; set; }
}
// Asynchronous query
public class GetUserAsyncQuery : IAsyncRequest<UserDto> { public int UserId { get; set; } }
// Asynchronous command
public class CreateUserAsyncCommand : IAsyncRequest<int>
{
public string Username { get; set; }
public string Email { get; set; }
}
Exception Handling with Behaviors
public class ErrorHandlingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<ErrorHandlingBehavior<TRequest, TResponse>> _logger;
public ErrorHandlingBehavior(ILogger<ErrorHandlingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public TResponse Handle(TRequest request, Func<TRequest, TResponse> next)
{
try
{
return next(request);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error during processing of {typeof(TRequest).Name}");
throw; // Or handle the exception appropriately
}
}
}
Best Practices
- Keep requests immutable: Define properties as
readonly
or use C# records - Use appropriate return types: Return
void
orTask
for commands, specific data for queries - Separate requests and handlers: Keep each handler in a separate file to improve code organization
- Use behaviors for cross-cutting concerns: Validation, logging, caching, etc.
- Order behaviors correctly: Use the
IOrderedPipelineBehavior
interface to control execution order - Choose the appropriate registration mode:
Startup
for maximum performance in productionLazyLoading
to reduce startup time and memory usageHybrid
for an optimal compromise
- Prefer asynchronous APIs for I/O-bound or potentially blocking operations
Contributing
Contributions are welcome! If you want to improve FastMediator, feel free to send a pull request.
License
FastMediator is distributed under the MIT license. See the LICENSE file for more details.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net8.0
- Microsoft.Extensions.DependencyInjection (>= 9.0.4)
- Microsoft.Extensions.Logging (>= 2.1.1)
- Scrutor (>= 6.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.