Vali-Mediator
2.0.1
dotnet add package Vali-Mediator --version 2.0.1
NuGet\Install-Package Vali-Mediator -Version 2.0.1
<PackageReference Include="Vali-Mediator" Version="2.0.1" />
<PackageVersion Include="Vali-Mediator" Version="2.0.1" />
<PackageReference Include="Vali-Mediator" />
paket add Vali-Mediator --version 2.0.1
#r "nuget: Vali-Mediator, 2.0.1"
#:package Vali-Mediator@2.0.1
#addin nuget:?package=Vali-Mediator&version=2.0.1
#tool nuget:?package=Vali-Mediator&version=2.0.1
Vali-Mediator
Vali-Mediator is a lightweight, zero-dependency mediator library for .NET 7, 8, and 9. It implements the CQRS pattern through requests, notifications (pub/sub), fire-and-forget commands, async streaming, pipeline behaviors, pre/post processors, Saga-pattern compensation flows, and a built-in Result<T> type. The library integrates seamlessly with Microsoft.Extensions.DependencyInjection and has no other external dependencies.
Installation
dotnet add package Vali-Mediator
Quick Start
// Program.cs / Startup.cs
builder.Services.AddValiMediator(config =>
{
// Register all handlers, processors, and stream handlers from an assembly
config.RegisterServicesFromAssemblyContaining<Program>();
// Or register by assembly reference
// config.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
Feature Overview
| Feature | Interface / Type | Description |
|---|---|---|
| Requests (query/command) | IRequest<TResponse>, IRequestHandler<TRequest, TResponse> |
Single handler, request-response |
| Void requests | IRequest, IRequestHandler<TRequest> |
Unit-returning shorthand |
| Notifications | INotification, INotificationHandler<T> |
Pub/sub, multiple handlers |
| Fire-and-forget | IFireAndForget, IFireAndForgetHandler<T> |
No response, side-effect commands |
| Streaming | IStreamRequest<T>, IStreamRequestHandler<TRequest, T> |
IAsyncEnumerable<T> |
| Pipeline behaviors | IPipelineBehavior<TRequest, TResponse> |
Cross-cutting concerns for requests |
| Dispatch behaviors | IPipelineBehavior<TRequest> |
Cross-cutting concerns for notifications/fire-and-forget |
| Pre/Post processors | IPreProcessor<T>, IPostProcessor<T> |
Hook before/after dispatch |
| Request processors | IPreProcessor<TRequest, TResponse>, IPostProcessor<TRequest, TResponse> |
Hook before/after request handling |
| Result type | Result<T>, Result |
Functional success/failure without exceptions |
| Compensation (Saga) | ICompensable, Compensable |
Rollback pattern for distributed flows |
Code Examples
Request with Response
// Define the request
public record GetUserQuery(int UserId) : IRequest<UserDto>;
// Define the handler
public class GetUserQueryHandler : IRequestHandler<GetUserQuery, UserDto>
{
public async Task<UserDto> Handle(GetUserQuery request, CancellationToken cancellationToken)
{
// ... fetch user
return new UserDto(request.UserId, "Alice");
}
}
// Send it
var user = await mediator.Send(new GetUserQuery(42));
Void Request (Unit shorthand)
public record DeleteUserCommand(int UserId) : IRequest;
public class DeleteUserCommandHandler : IRequestHandler<DeleteUserCommand>
{
public async Task<Unit> Handle(DeleteUserCommand request, CancellationToken cancellationToken)
{
// delete user ...
return Unit.Value;
}
}
await mediator.Send(new DeleteUserCommand(42));
Result<T> with Map/Bind chain
public record CreateOrderCommand(int CustomerId, decimal Amount) : IRequest<Result<int>>;
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Result<int>>
{
public Task<Result<int>> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
if (request.Amount <= 0)
return Task.FromResult(Result<int>.Fail("Amount must be positive.", ErrorType.Validation));
return Task.FromResult(Result<int>.Ok(99)); // new order id
}
}
// Usage with functional chaining
var result = await mediator.Send(new CreateOrderCommand(1, 100m));
var invoiceResult = result
.Map(orderId => $"Invoice for order #{orderId}")
.Tap(msg => Console.WriteLine(msg))
.OnFailure((err, type) => Console.Error.WriteLine($"[{type}] {err}"));
string output = result.Match(
onSuccess: id => $"Created order {id}",
onFailure: (err, _) => $"Error: {err}"
);
Result (non-generic) for void handlers
public record ArchiveUserCommand(int UserId) : IRequest<Result>;
public class ArchiveUserHandler : IRequestHandler<ArchiveUserCommand, Result>
{
public Task<Result> Handle(ArchiveUserCommand request, CancellationToken cancellationToken)
{
// ... attempt archive
bool success = true;
return Task.FromResult(success ? Result.Ok() : Result.Fail("User not found.", ErrorType.NotFound));
}
}
var result = await mediator.Send(new ArchiveUserCommand(42));
result.Tap(() => Console.WriteLine("Archived"))
.OnFailure((err, type) => Console.Error.WriteLine($"[{type}] {err}"));
Notification with Priority
public record OrderCreatedEvent(int OrderId) : INotification;
public class EmailNotificationHandler : INotificationHandler<OrderCreatedEvent>
{
public int Priority => 10; // runs first
public Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
{
// send email
return Task.CompletedTask;
}
}
public class AuditLogHandler : INotificationHandler<OrderCreatedEvent>
{
public int Priority => 5; // runs second
public Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
{
// write audit log
return Task.CompletedTask;
}
}
// Sequential (default) — respects priority order
await mediator.Publish(new OrderCreatedEvent(99));
// Parallel — all handlers concurrently
await mediator.Publish(new OrderCreatedEvent(99), PublishStrategy.Parallel);
// ResilientParallel — all run, failures collected as AggregateException
await mediator.Publish(new OrderCreatedEvent(99), PublishStrategy.ResilientParallel);
Fire-and-Forget
public record SendWelcomeEmailCommand(string Email) : IFireAndForget;
public class SendWelcomeEmailHandler : IFireAndForgetHandler<SendWelcomeEmailCommand>
{
public Task Handle(SendWelcomeEmailCommand command, CancellationToken cancellationToken)
{
// send email asynchronously
return Task.CompletedTask;
}
}
await mediator.Send(new SendWelcomeEmailCommand("alice@example.com"));
Pipeline Behavior
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
=> _logger = logger;
public async Task<TResponse> Handle(TRequest request, Func<Task<TResponse>> next, CancellationToken cancellationToken)
{
_logger.LogInformation("Handling {Request}", typeof(TRequest).Name);
var response = await next();
_logger.LogInformation("Handled {Request}", typeof(TRequest).Name);
return response;
}
}
// Registration — use generic shorthand (open-generic type required)
builder.Services.AddValiMediator(config =>
{
config.RegisterServicesFromAssemblyContaining<Program>()
.AddRequestBehavior<LoggingBehavior<,>>();
});
Streaming
public record GetProductsStream(string Category) : IStreamRequest<ProductDto>;
public class GetProductsStreamHandler : IStreamRequestHandler<GetProductsStream, ProductDto>
{
public async IAsyncEnumerable<ProductDto> Handle(
GetProductsStream request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
for (int i = 0; i < 100; i++)
{
cancellationToken.ThrowIfCancellationRequested();
yield return new ProductDto(i, $"Product {i}");
await Task.Delay(10, cancellationToken);
}
}
}
await foreach (var product in mediator.CreateStream(new GetProductsStream("Electronics")))
{
Console.WriteLine(product.Name);
}
Saga Compensation (ICompensable / Compensable)
public class PlaceOrderCommand : Compensable, IRequest<Result<int>>
{
public int CustomerId { get; init; }
// Define the rollback action
public override IFireAndForget? GetCompensation()
=> new CancelOrderCommand(CustomerId);
}
public record CancelOrderCommand(int CustomerId) : IFireAndForget;
// In a handler or orchestration:
var command = new PlaceOrderCommand { CustomerId = 1 };
try
{
var result = await mediator.Send(command);
}
catch
{
await command.Compensate(mediator);
}
Pre/Post Processors
// Pre-processor for a notification
public class OrderAuditPreProcessor : IPreProcessor<OrderCreatedEvent>
{
public Task Process(OrderCreatedEvent notification, CancellationToken cancellationToken)
{
Console.WriteLine($"Before handling OrderCreatedEvent #{notification.OrderId}");
return Task.CompletedTask;
}
}
// Pre-processor for a request
public class ValidationPreProcessor<TRequest, TResponse> : IPreProcessor<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public Task Process(TRequest request, CancellationToken cancellationToken)
{
// validate request before it reaches the handler
return Task.CompletedTask;
}
}
Donations
If Vali-Mediator is useful to you, consider supporting its development:
- Latin America — MercadoPago
- International — PayPal
License
Contributions
Issues and pull requests are welcome on GitHub.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. 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 is compatible. 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. |
-
net7.0
-
net8.0
-
net9.0
NuGet packages (6)
Showing the top 5 NuGet packages that depend on Vali-Mediator:
| Package | Downloads |
|---|---|
|
Vali-Mediator.Resilience
Zero-dependency resilience library for .NET 7/8/9. Provides Retry (with jitter/backoff), Circuit Breaker (sliding-window state machine), Timeout (optimistic + pessimistic), Fallback, Bulkhead, Rate Limiter (partitioned per-user/IP), Hedge, and Chaos policies via a fluent builder API. Attach policies to Vali-Mediator requests via IResiliencePolicyProvider<T> (no class needed), with global fallback policy support. No Polly or other external dependencies required. |
|
|
Vali-Validation.ValiMediator
Vali-Mediator integration for Vali-Validation. Adds a ValidationBehavior<TRequest, TResponse> that validates IRequest objects before they reach their handler. When TResponse is Result<T>, validation failures are returned as Result.Fail(errors, ErrorType.Validation) — no exceptions needed. For other response types, throws ValidationException. Package name: Vali-Validation.ValiMediator. Requires Vali-Mediator. |
|
|
Vali-Mediator.AspNetCore
ASP.NET Core integration for Vali-Mediator. Maps Result<T> and Result to HTTP responses: Ok→200, Validation→400, NotFound→404, Conflict→409, Unauthorized→401, Forbidden→403, Failure→500. Provides ToActionResult() for MVC controllers and ToHttpResult() for Minimal API. |
|
|
Vali-Mediator.Observability
Zero-dependency observability library for Vali-Mediator (.NET 7/8/9). Provides OpenTelemetry-compatible ActivitySource tracing, pluggable metrics via IMetricsCollector, and structured request lifecycle observers via IRequestObserver. Includes pipeline behaviours for IRequest and IDispatch (INotification / IFireAndForget), console implementations for quick diagnostics, and a fluent DI registration API. No OpenTelemetry SDK or other external dependencies required. |
|
|
Vali-Mediator.Caching
Caching integration library for Vali-Mediator (.NET 7/8/9). Provides a pluggable ICacheStore abstraction, an in-memory implementation with expiry and group-based invalidation, and two pipeline behaviors: CachingBehavior (read-through / write-through) and CacheInvalidationBehavior. Zero external dependencies beyond Microsoft.Extensions.DependencyInjection.Abstractions and Vali-Mediator. |
GitHub repositories
This package is not used by any popular GitHub repositories.
v2.0.1 — Documentation:
- Enabled XML documentation file generation (GenerateDocumentationFile) — full IntelliSense for all public APIs.
- Added missing XML doc comments on Unit, Result, and ValiMediator members.
- Fixed ambiguous and unresolved cref references in doc comments.
v2.0.0 — Major release:
New features:
- Result<T>: Map(), Bind(), MapAsync(), BindAsync(), Tap(), OnFailure() functional methods.
- Result<T>: ValidationErrors property (Dictionary per property) on validation failures.
- Result (non-generic): for void-returning handlers that can fail — Result.Ok() / Result.Fail().
- IRequestHandler<TRequest> shorthand for Unit-returning handlers.
- PublishStrategy.ResilientParallel: all handlers run, exceptions collected as AggregateException.
- RegisterServicesFromAssemblyContaining<T>(): convenience assembly registration.
- AddRequestBehavior<TImpl>() / AddDispatchBehavior<TImpl>(): generic behavior registration.
- SendOrDefault<TResponse>: returns default instead of throwing when no handler is found.
- Source Link + symbol packages (.snupkg) for NuGet debugging.
Already in v1.x (now documented):
- IStreamRequest<T> / CreateStream<T>: async enumerable streaming.
- IFireAndForget / IFireAndForgetHandler<T>: fire-and-forget commands with pipeline.
- PublishStrategy.Sequential / Parallel for notifications.
- Pipeline behaviors for requests (IPipelineBehavior<TRequest,TResponse>) and dispatch (IPipelineBehavior<TRequest>).
- IPreProcessor / IPostProcessor for all dispatch types — auto-discovered from assembly.
- ICompensable / Compensable: Saga-pattern compensation flows.
- ServiceLifetime control per assembly and per behavior registration.
- INotificationHandler.Priority: handlers ordered by descending priority.