Vali-Mediator 2.0.1

dotnet add package Vali-Mediator --version 2.0.1
                    
NuGet\Install-Package Vali-Mediator -Version 2.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Vali-Mediator" Version="2.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Vali-Mediator" Version="2.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Vali-Mediator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Vali-Mediator --version 2.0.1
                    
#r "nuget: Vali-Mediator, 2.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Vali-Mediator@2.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Vali-Mediator&version=2.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Vali-Mediator&version=2.0.1
                    
Install as a Cake Tool

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:


License

Apache License 2.0

Contributions

Issues and pull requests are welcome on GitHub.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
2.0.1 216 3/18/2026
2.0.0 446 3/16/2026
1.2.0 183 4/29/2025
1.1.0 218 4/7/2025
1.0.0 225 4/3/2025

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.