Hecole.Mediator 1.3.0

dotnet add package Hecole.Mediator --version 1.3.0
                    
NuGet\Install-Package Hecole.Mediator -Version 1.3.0
                    
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="Hecole.Mediator" Version="1.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Hecole.Mediator" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Hecole.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 Hecole.Mediator --version 1.3.0
                    
#r "nuget: Hecole.Mediator, 1.3.0"
                    
#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 Hecole.Mediator@1.3.0
                    
#: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=Hecole.Mediator&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Hecole.Mediator&version=1.3.0
                    
Install as a Cake Tool

🇧🇷 Português | 🇺🇸 English

🧩 Hecole.Mediator

NuGet NuGet Downloads .NET License: MIT

A lightweight and performant implementation of the Mediator pattern for .NET 8 and .NET 10, inspired by MediatR.

Designed for modular projects based on Clean Architecture, with support for CQRS (Commands, Queries, Requests, and Notifications), async behavior pipelines (validation, logging, performance monitoring, and exception handling), and native integration with Microsoft.Extensions.DependencyInjection.

Ideal for systems with independent modules that need use case orchestration with low coupling.


🚀 Key Features

  • ✅ CQRS Support — IRequest<TResponse>, IRequestHandler<TRequest, TResponse>, INotification, and INotificationHandler<TNotification>.

  • 🧠 Async Pipeline Behaviors — Middleware chain for cross-cutting concerns: async validation with FluentValidation (ValidateAsync + MustAsync — fixed in v1.2.0), structured logging, performance monitoring, and unhandled exception capture.

  • ⚙️ Auto-Registration via DI — AddHecoleMediator extension for automatic assembly scanning and handler/behavior registration.

  • ⚡ Optimized Performance — Reflection invoker caching with ConcurrentDictionary to avoid reflection on the hot path; parallel notification dispatch via Task.WhenAll.

  • 🧱 Robustness — Isolated exception handling per notification handler, multiple validator support, and fire-and-forget notification semantics.

  • 🎯 Multi-Targeting — Supports net8.0 and net10.0 in the same NuGet package. The runtime resolves the correct target automatically.

  • 🧩 Optional Dependencies — Core depends only on Microsoft.Extensions.DependencyInjection. FluentValidation and Logging are optional.

  • 🧭 Clean Architecture Aligned — Pure interfaces for Domain/SharedKernel and pluggable implementations in the Infrastructure layer.


🧰 Requirements

  • .NET 8.0 or .NET 10.0 or later.
  • Optional packages:
    • FluentValidation (for ValidationBehavior)
    • Microsoft.Extensions.Logging (for structured logging)

💾 Installation

Via NuGet:

dotnet add package Hecole.Mediator

Or as a project reference:

git clone https://github.com/mvsergio/Hecole.Mediator.git
<ProjectReference Include="..\Hecole.Mediator\Hecole.Mediator.csproj" />

🧠 Usage

DI Registration (Program.cs)

using Hecole.Mediator.Implementation.Extensions;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);

// Register Mediator and scan assemblies (e.g., Application layer)
builder.Services.AddHecoleMediator(
    Assembly.GetAssembly(typeof(CreateInstitutionCommandHandler))
);

// Global behaviors (registration order matters: first registered = outermost)
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PerformanceBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

// Register validators
builder.Services.AddTransient<IValidator<CreateInstitutionCommand>, CreateInstitutionCommandValidator>();

AddHecoleMediator registers ICoreMediator as Scoped (recommended — works with scoped DbContext).

AddCoreMediator registers ICoreMediator as Singleton (use when all dependencies are also singletons).


Command / Query Handler

using Hecole.Mediator.Interfaces;

public record CreateInstitutionCommand(string Name) : IRequest<CreateInstitutionResult>;
public record CreateInstitutionResult(Guid Id);

public class CreateInstitutionCommandHandler
    : IRequestHandler<CreateInstitutionCommand, CreateInstitutionResult>
{
    private readonly IRepository _repository;

    public CreateInstitutionCommandHandler(IRepository repository)
    {
        _repository = repository;
    }

    public async Task<CreateInstitutionResult> Handle(
        CreateInstitutionCommand request,
        CancellationToken cancellationToken)
    {
        var id = await _repository.SaveAsync(request, cancellationToken);
        return new CreateInstitutionResult(id);
    }
}

Notification Handler

using Hecole.Mediator.Interfaces;

public record InstitutionCreatedEvent(Guid Id, string Name) : INotification;

public class SendWelcomeEmailHandler : INotificationHandler<InstitutionCreatedEvent>
{
    public async Task Handle(InstitutionCreatedEvent notification, CancellationToken cancellationToken)
    {
        // Send welcome email
        await Task.CompletedTask;
    }
}

Notifications are dispatched to all registered handlers in parallel via Task.WhenAll. If one handler throws, the others still execute — the exception propagates to the caller after all handlers complete.


Controller

using Hecole.Mediator.Interfaces;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/institutions")]
public class InstitutionController : ControllerBase
{
    private readonly ICoreMediator _mediator;

    public InstitutionController(ICoreMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> Create(
        [FromBody] CreateInstitutionCommand command,
        CancellationToken cancellationToken)
    {
        var result = await _mediator.Send(command, cancellationToken);
        return Ok(result);
    }
}

Validation with MustAsync() (fixed in v1.2.0)

using FluentValidation;

public class CreateInstitutionCommandValidator : AbstractValidator<CreateInstitutionCommand>
{
    public CreateInstitutionCommandValidator(IRepository repository)
    {
        RuleFor(x => x.Name)
            .NotEmpty().WithMessage("Name is required")
            .MaximumLength(200).WithMessage("Name too long");

        RuleFor(x => x.Name)
            .MustAsync(async (name, ct) =>
            {
                return !await repository.ExistsAsync(name, ct);
            })
            .WithMessage("An institution with this name already exists");
    }
}

In v1.1.0 and earlier, MustAsync() rules were silently ignored because ValidationBehavior called the synchronous Validate(). Since v1.2.0, it correctly uses ValidateAsync() with Task.WhenAll, and the CancellationToken is propagated.


Custom Behavior

using Hecole.Mediator.Interfaces;
using Hecole.Mediator.Interfaces.Behaviors;

public class AuditBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        // Before handler
        Console.WriteLine($"Processing {typeof(TRequest).Name}");

        var response = await next();

        // After handler
        Console.WriteLine($"Completed {typeof(TRequest).Name}");

        return response;
    }
}

Register it in the DI container:

builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(AuditBehavior<,>));

🔧 Built-in Behaviors

Behavior Description
ValidationBehavior<,> Runs all IValidator<TRequest> via ValidateAsync. Throws ValidationException on failure.
LoggingBehavior<,> Logs request start/end with elapsed time.
PerformanceBehavior<,> Logs a warning when a request takes longer than 500ms.
UnhandledExceptionBehavior<,> Catches and logs unhandled exceptions, then re-throws.

🤝 Contributing

Contributions are welcome!

git clone https://github.com/mvsergio/Hecole.Mediator.git
git checkout -b feature/my-feature
git commit -m "Add my feature"
git push origin feature/my-feature

Open a Pull Request with a description of your changes. Please add unit tests and follow the existing code style (#nullable enable, async/await).


⚖️ License

Distributed under the MIT License. See LICENSE for details.


📋 Changelog

See CHANGELOG.md for a full list of changes.



🇧🇷 Português

🇧🇷 Versão completa em português

NuGet NuGet Downloads .NET License: MIT

Uma implementação leve e performática do padrão Mediator para .NET 8 e .NET 10, inspirada no MediatR.

Projetada para projetos modulares baseados em Clean Architecture, com suporte a CQRS (Commands, Queries, Requests e Notifications), pipelines de behaviors assíncronos (validação, logging, monitoramento de performance e tratamento de exceções) e integração nativa com Microsoft.Extensions.DependencyInjection.

Ideal para sistemas com módulos independentes que precisam de orquestração de use cases com baixo acoplamento.


🚀 Features Principais

  • ✅ Suporte a CQRS — IRequest<TResponse>, IRequestHandler<TRequest, TResponse>, INotification e INotificationHandler<TNotification>.

  • 🧠 Pipeline de Behaviors Assíncronos — Cadeia de middlewares para cross-cutting concerns: validação assíncrona com FluentValidation (ValidateAsync + MustAsync — corrigido na v1.2.0), logging estruturado, monitoramento de performance e captura de exceções.

  • ⚙️ Registro Automático via DI — Extensão AddHecoleMediator para scan automático de assemblies e registro de handlers/behaviors.

  • ⚡ Performance Otimizada — Caching de invokers com ConcurrentDictionary para evitar reflection no hot path; execução paralela de notifications via Task.WhenAll.

  • 🧱 Robustez — Tratamento isolado de exceções por notification handler, suporte a múltiplos validators e semântica fire-and-forget para notifications.

  • 🎯 Multi-Targeting — Suporte a net8.0 e net10.0 no mesmo pacote NuGet. O runtime resolve o target correto automaticamente.

  • 🧩 Dependências Opcionais — Core depende apenas de Microsoft.Extensions.DependencyInjection. FluentValidation e Logging são opcionais.

  • 🧭 Alinhado com Clean Architecture — Interfaces puras para Domain/SharedKernel e implementações plugáveis na camada Infrastructure.


🧰 Requisitos

  • .NET 8.0 ou .NET 10.0 ou superior.
  • Pacotes opcionais:
    • FluentValidation (para ValidationBehavior)
    • Microsoft.Extensions.Logging (para logging estruturado)

💾 Instalação

Via NuGet:

dotnet add package Hecole.Mediator

Ou como referência de projeto:

git clone https://github.com/mvsergio/Hecole.Mediator.git
<ProjectReference Include="..\Hecole.Mediator\Hecole.Mediator.csproj" />

🧠 Uso

Registro no DI (Program.cs)
using Hecole.Mediator.Implementation.Extensions;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);

// Registra o Mediator e escaneia assemblies (ex.: camada Application)
builder.Services.AddHecoleMediator(
    Assembly.GetAssembly(typeof(CreateInstitutionCommandHandler))
);

// Behaviors globais (ordem de registro importa: primeiro registrado = mais externo)
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PerformanceBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

// Registrar validators
builder.Services.AddTransient<IValidator<CreateInstitutionCommand>, CreateInstitutionCommandValidator>();

AddHecoleMediator registra ICoreMediator como Scoped (recomendado — funciona com DbContext scoped).

AddCoreMediator registra ICoreMediator como Singleton (use quando todas as dependências também são singletons).


Handler de Command / Query
using Hecole.Mediator.Interfaces;

public record CreateInstitutionCommand(string Name) : IRequest<CreateInstitutionResult>;
public record CreateInstitutionResult(Guid Id);

public class CreateInstitutionCommandHandler
    : IRequestHandler<CreateInstitutionCommand, CreateInstitutionResult>
{
    private readonly IRepository _repository;

    public CreateInstitutionCommandHandler(IRepository repository)
    {
        _repository = repository;
    }

    public async Task<CreateInstitutionResult> Handle(
        CreateInstitutionCommand request,
        CancellationToken cancellationToken)
    {
        var id = await _repository.SaveAsync(request, cancellationToken);
        return new CreateInstitutionResult(id);
    }
}

Handler de Notification
using Hecole.Mediator.Interfaces;

public record InstitutionCreatedEvent(Guid Id, string Name) : INotification;

public class SendWelcomeEmailHandler : INotificationHandler<InstitutionCreatedEvent>
{
    public async Task Handle(InstitutionCreatedEvent notification, CancellationToken cancellationToken)
    {
        // Enviar e-mail de boas-vindas
        await Task.CompletedTask;
    }
}

Notifications são despachadas para todos os handlers registrados em paralelo via Task.WhenAll. Se um handler lançar exceção, os demais ainda executam — a exceção é propagada ao caller após todos os handlers completarem.


Controller
using Hecole.Mediator.Interfaces;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/institutions")]
public class InstitutionController : ControllerBase
{
    private readonly ICoreMediator _mediator;

    public InstitutionController(ICoreMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> Create(
        [FromBody] CreateInstitutionCommand command,
        CancellationToken cancellationToken)
    {
        var result = await _mediator.Send(command, cancellationToken);
        return Ok(result);
    }
}

Validação com MustAsync() (corrigido na v1.2.0)
using FluentValidation;

public class CreateInstitutionCommandValidator : AbstractValidator<CreateInstitutionCommand>
{
    public CreateInstitutionCommandValidator(IRepository repository)
    {
        RuleFor(x => x.Name)
            .NotEmpty().WithMessage("Name is required")
            .MaximumLength(200).WithMessage("Name too long");

        RuleFor(x => x.Name)
            .MustAsync(async (name, ct) =>
            {
                return !await repository.ExistsAsync(name, ct);
            })
            .WithMessage("An institution with this name already exists");
    }
}

Na v1.1.0 e anteriores, regras MustAsync() eram ignoradas silenciosamente porque o ValidationBehavior chamava Validate() (síncrono). Desde a v1.2.0, ele usa corretamente ValidateAsync() com Task.WhenAll, e o CancellationToken é propagado.


Custom Behavior
using Hecole.Mediator.Interfaces;
using Hecole.Mediator.Interfaces.Behaviors;

public class AuditBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken)
    {
        // Antes do handler
        Console.WriteLine($"Processando {typeof(TRequest).Name}");

        var response = await next();

        // Depois do handler
        Console.WriteLine($"Concluído {typeof(TRequest).Name}");

        return response;
    }
}

Registre no container DI:

builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(AuditBehavior<,>));

🔧 Behaviors Incluídos

Behavior Descrição
ValidationBehavior<,> Executa todos os IValidator<TRequest> via ValidateAsync. Lança ValidationException em caso de falha.
LoggingBehavior<,> Loga início/fim do request com tempo decorrido.
PerformanceBehavior<,> Loga warning quando um request leva mais de 500ms.
UnhandledExceptionBehavior<,> Captura e loga exceções não tratadas, depois re-lança.

🤝 Contribuições

Contribuições são bem-vindas!

git clone https://github.com/mvsergio/Hecole.Mediator.git
git checkout -b feature/minha-feature
git commit -m "Adiciona minha feature"
git push origin feature/minha-feature

Abra um Pull Request com uma descrição das alterações. Adicione testes unitários e siga o estilo de código existente (#nullable enable, async/await).


⚖️ Licença

Distribuído sob a MIT License. Veja LICENSE para detalhes.


📋 Changelog

Veja CHANGELOG.md para a lista completa de mudanças.

Product 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 is compatible.  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

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
1.3.0 131 4/28/2026
1.2.1 285 3/30/2026
1.2.0 117 3/30/2026
1.1.0 385 3/22/2026
1.0.0 217 11/6/2025

1.3.0 (2026-04-28) — Fix open-generic IPipelineBehavior registration in AddHecoleMediator. Open-generic behaviors (ex: ObservabilityBehavior<TRequest, TResponse>) were registered with parameterized types causing ArgumentException at BuildServiceProvider. Now correctly registered as (typeof(IPipelineBehavior<,>), implementationType) when type.IsGenericTypeDefinition. Tests added (AddHecoleMediatorTests). No breaking change — workaround manual via services.Remove + AddTransient open-generic can be removed.