FlowR.Mediator
2.0.2
dotnet add package FlowR.Mediator --version 2.0.2
NuGet\Install-Package FlowR.Mediator -Version 2.0.2
<PackageReference Include="FlowR.Mediator" Version="2.0.2" />
<PackageVersion Include="FlowR.Mediator" Version="2.0.2" />
<PackageReference Include="FlowR.Mediator" />
paket add FlowR.Mediator --version 2.0.2
#r "nuget: FlowR.Mediator, 2.0.2"
#:package FlowR.Mediator@2.0.2
#addin nuget:?package=FlowR.Mediator&version=2.0.2
#tool nuget:?package=FlowR.Mediator&version=2.0.2
FlowR 🌊
A free, high-performance, open-source mediator pattern library for .NET — forever.
FlowR brings the mediator/CQRS pattern to .NET with zero licensing fees, excellent performance through compiled expression trees, and more features out of the box than alternatives.
Why FlowR?
| Feature | FlowR | MediatR |
|---|---|---|
| License | ✅ MIT (free forever) | ❌ Paid for commercial |
| Request/Response | ✅ | ✅ |
| Void Commands | ✅ | ✅ |
| Notifications | ✅ | ✅ |
| Streaming | ✅ | ✅ |
| Pipeline Behaviors | ✅ | ✅ |
| Notification Strategies | ✅ Sequential, Parallel, FireAndForget | ❌ |
| Built-in Logging Behavior | ✅ | ❌ |
| Built-in Validation Behavior | ✅ | ❌ |
| Built-in Retry Behavior | ✅ | ❌ |
| Built-in Performance Behavior | ✅ | ❌ |
| Notification Pipeline Behaviors | ✅ | ❌ |
| Compiled Expression Trees (fast!) | ✅ | ✅ |
Installation
dotnet add package FlowR.Mediator
Quick Start
1. Register FlowR
// Program.cs
builder.Services.AddFlowR(typeof(Program).Assembly);
2. Define a Request
// Request (returns a response)
public record GetUserQuery(int UserId) : IRequest<UserDto>;
public record UserDto(int Id, string Name);
// Command (no response)
public record CreateUserCommand(string Name, string Email) : IRequest;
3. Create a Handler
public class GetUserQueryHandler : IRequestHandler<GetUserQuery, UserDto>
{
private readonly IUserRepository _repo;
public GetUserQueryHandler(IUserRepository repo) => _repo = repo;
public async Task<UserDto> HandleAsync(GetUserQuery request, CancellationToken cancellationToken = default)
{
var user = await _repo.GetByIdAsync(request.UserId, cancellationToken);
return new UserDto(user.Id, user.Name);
}
}
4. Send It
public class UserController : ControllerBase
{
private readonly ISender _sender;
public UserController(ISender sender) => _sender = sender;
[HttpGet("{id}")]
public async Task<UserDto> GetUser(int id)
=> await _sender.SendAsync(new GetUserQuery(id));
}
Notifications
Publish events to multiple handlers:
// Define
public record UserRegisteredEvent(int UserId) : INotification;
// Multiple handlers
public class SendWelcomeEmailHandler : INotificationHandler<UserRegisteredEvent> { ... }
public class CreateDefaultProfileHandler : INotificationHandler<UserRegisteredEvent> { ... }
// Publish
await mediator.PublishAsync(new UserRegisteredEvent(userId));
// Publish in parallel
await mediator.PublishAsync(new UserRegisteredEvent(userId), NotificationPublishStrategy.Parallel);
// Fire and forget (background, non-critical)
await mediator.PublishAsync(new UserRegisteredEvent(userId), NotificationPublishStrategy.FireAndForget);
Streaming
public record GetOrdersStream(int CustomerId) : IStreamRequest<OrderDto>;
public class GetOrdersStreamHandler : IStreamRequestHandler<GetOrdersStream, OrderDto>
{
public async IAsyncEnumerable<OrderDto> HandleAsync(GetOrdersStream request, CancellationToken cancellationToken = default)
{
await foreach (var order in _db.GetOrdersAsync(request.CustomerId, cancellationToken))
yield return new OrderDto(order.Id, order.Total);
}
}
// Use
await foreach (var order in sender.CreateStream(new GetOrdersStream(customerId)))
Console.WriteLine(order);
Pipeline Behaviors
Add cross-cutting concerns to your request pipeline:
public class AuthorizationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ICurrentUser _currentUser;
public AuthorizationBehavior(ICurrentUser currentUser) => _currentUser = currentUser;
public async Task<TResponse> HandleAsync(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken = default)
{
if (!_currentUser.IsAuthenticated)
throw new UnauthorizedAccessException();
return await next();
}
}
// Register for all requests (open generic)
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(AuthorizationBehavior<,>));
Built-in Behaviors
FlowR ships with ready-to-use behaviors:
// Logging (logs request name and duration)
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
// Performance (warns on slow requests > 500ms)
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PerformanceBehavior<,>));
// Validation (integrates with any validator)
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
services.AddTransient<IFlowRValidator<CreateUserCommand>, CreateUserCommandValidator>();
// Retry (retries failed requests up to N times)
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RetryBehavior<,>));
Validation
public class CreateUserCommandValidator : IFlowRValidator<CreateUserCommand>
{
public Task<IEnumerable<ValidationError>> ValidateAsync(CreateUserCommand request, CancellationToken cancellationToken = default)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(request.Name))
errors.Add(new ValidationError("Name", "Name is required."));
if (!request.Email.Contains('@'))
errors.Add(new ValidationError("Email", "Invalid email address."));
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
}
When validation fails, FlowRValidationException is thrown with all errors:
catch (FlowRValidationException ex)
{
foreach (var error in ex.Errors)
Console.WriteLine($"{error.PropertyName}: {error.ErrorMessage}");
}
Configuration
services.AddFlowR(options =>
{
options.MediatorLifetime = ServiceLifetime.Scoped; // default: Transient
options.HandlerLifetime = ServiceLifetime.Transient; // default: Transient
options.BehaviorLifetime = ServiceLifetime.Transient; // default: Transient
},
typeof(Program).Assembly,
typeof(OtherAssemblyMarker).Assembly);
Performance
FlowR uses compiled expression trees to build handler delegates once and cache them. After the first call, handler resolution is essentially a dictionary lookup — no reflection overhead on hot paths.
License
MIT — free for personal and commercial use, forever.
Contributing
PRs welcome! See CONTRIBUTING.md.
Star ⭐ the repo if FlowR helps you!
| 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 (>= 8.0.0)
- Microsoft.Extensions.Logging (>= 8.0.0)
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 |
|---|---|---|
| 2.0.2 | 257 | 4/24/2026 |
| 2.0.1 | 114 | 4/24/2026 |
| 2.0.1-pr.2.4f56e93 | 68 | 4/24/2026 |
| 2.0.0 | 113 | 4/24/2026 |
| 2.0.0-alpha.8c5a067 | 72 | 4/24/2026 |
| 1.0.1 | 124 | 4/14/2026 |
| 1.0.1-alpha.a55379a | 69 | 4/23/2026 |
| 1.0.0 | 122 | 3/19/2026 |