HermesMediator.Testing 1.0.2

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package HermesMediator.Testing --version 1.0.2
                    
NuGet\Install-Package HermesMediator.Testing -Version 1.0.2
                    
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="HermesMediator.Testing" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HermesMediator.Testing" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="HermesMediator.Testing" />
                    
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 HermesMediator.Testing --version 1.0.2
                    
#r "nuget: HermesMediator.Testing, 1.0.2"
                    
#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 HermesMediator.Testing@1.0.2
                    
#: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=HermesMediator.Testing&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=HermesMediator.Testing&version=1.0.2
                    
Install as a Cake Tool

Hermes

High-performance mediator for .NET 10 — zero-reflection hot path, built-in OpenTelemetry, compile-time safety.

Por que Hermes?

Aspecto MediatR Hermes
Dispatch Reflection em toda chamada Source Generator gera tabela em compile-time — zero reflection no hot path
Startup Assembly scanning em runtime (lento) Registro estático gerado — startup instantâneo
Handler não encontrado Exceção em runtime, mensagem genérica Analyzer HRM001 alerta em compile-time
Ordem de behaviors Depende da ordem de registro — frágil Determinística e explícita
Behaviors condicionais Apenas via generic constraint IConditionalBehavior para condições em runtime
Timeout por request Manual [RequestTimeout(ms)] declarativo
Ignorar behavior Não existe [IgnoreBehavior(typeof(...))] por request
Diagnósticos Nenhum built-in ActivitySource + Meter integrados (OpenTelemetry)
Contexto no pipeline Não existe IPipelineContextAccessor injetável
Testes Requer mocks verbosos FakeMediator com API fluente
Exception side-effects IRequestExceptionAction (sempre relança) ✅ igual
Licença Comercial a partir da v13 MIT

Instalação

dotnet add package Hermes
dotnet add package Hermes.Testing  # apenas em projetos de teste

Configuração

builder.Services.AddHermes(cfg => cfg
    .RegisterServicesFromAssembly(typeof(Program).Assembly)
    .AddOpenBehavior(typeof(LoggingBehavior<,>)));

Uso

Request com retorno

public record GetUserQuery(int Id) : IRequest<User>;

public class GetUserQueryHandler : IRequestHandler<GetUserQuery, User>
{
    public Task<User> Handle(GetUserQuery request, CancellationToken ct)
        => _db.Users.FindAsync(request.Id, ct);
}

// Envio
var user = await mediator.Send(new GetUserQuery(42));

Command (sem retorno)

public record CreateUserCommand(string Name) : IRequest;

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand>
{
    public async Task<Unit> Handle(CreateUserCommand request, CancellationToken ct)
    {
        await _db.Users.AddAsync(new User(request.Name), ct);
        return Unit.Value;
    }
}

Notificações

public record UserCreated(int UserId) : INotification;

// Múltiplos handlers — todos recebem a notificação
public class SendWelcomeEmail : INotificationHandler<UserCreated> { ... }
public class UpdateAuditLog  : INotificationHandler<UserCreated> { ... }

await mediator.Publish(new UserCreated(userId));

Streams

public record GetOrdersQuery(int CustomerId) : IStreamRequest<Order>;

public class GetOrdersHandler : IStreamRequestHandler<GetOrdersQuery, Order>
{
    public async IAsyncEnumerable<Order> Handle(GetOrdersQuery request, CancellationToken ct)
    {
        await foreach (var order in _db.GetOrdersAsync(request.CustomerId, ct))
            yield return order;
    }
}

await foreach (var order in mediator.CreateStream(new GetOrdersQuery(customerId)))
    Process(order);

Pipeline Behaviors

public class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : notnull
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        logger.LogInformation("Handling {Request}", typeof(TRequest).Name);
        var response = await next();
        logger.LogInformation("Handled {Request}", typeof(TRequest).Name);
        return response;
    }
}

// Registro
cfg.AddOpenBehavior(typeof(LoggingBehavior<,>));

Behavior Condicional

public class CacheBehavior<TRequest, TResponse>(ICache cache)
    : IConditionalBehavior<TRequest, TResponse>
    where TRequest : ICacheableRequest
{
    // Se retornar false, o behavior é pulado automaticamente — sem overhead
    public bool ShouldHandle(TRequest request) => !request.BypassCache;

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        if (cache.TryGet(request.CacheKey, out TResponse? cached)) return cached!;
        var result = await next();
        cache.Set(request.CacheKey, result);
        return result;
    }
}

Atributos

Timeout automático

[RequestTimeout(5000)] // cancela automaticamente após 5 segundos
public record GetReportQuery : IRequest<Report>;

Ignorar behavior por request

[IgnoreBehavior(typeof(LoggingBehavior<,>))]
public record HealthCheckQuery : IRequest<bool>;

Tratamento de Exceções

Handler (pode suprimir a exceção)

public class NotFoundHandler : IRequestExceptionHandler<GetUserQuery, User, NotFoundException>
{
    public Task Handle(GetUserQuery request, NotFoundException ex,
        RequestExceptionHandlerState<User> state, CancellationToken ct)
    {
        state.SetHandled(User.Anonymous); // suprime a exceção e retorna alternativo
        return Task.CompletedTask;
    }
}

Action (side-effect, sempre relança)

public class ErrorLogger : IRequestExceptionAction<GetUserQuery, Exception>
{
    public Task Execute(GetUserQuery request, Exception ex, CancellationToken ct)
    {
        logger.LogError(ex, "Erro ao processar {Request}", request);
        return Task.CompletedTask; // exceção é repropagada automaticamente
    }
}

Pre/Post Processors

public class ValidationPreProcessor<TRequest>(IValidator<TRequest> validator)
    : IRequestPreProcessor<TRequest>
    where TRequest : notnull
{
    public async Task Process(TRequest request, CancellationToken ct)
    {
        var result = await validator.ValidateAsync(request, ct);
        if (!result.IsValid) throw new ValidationException(result.Errors);
    }
}

Contexto de Pipeline

public class AuditBehavior<TRequest, TResponse>(IPipelineContextAccessor accessor)
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : notnull
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        accessor.Context!.Set("userId", GetCurrentUserId());
        return await next();
    }
}

// No handler ou em outro behavior:
var userId = accessor.Context?.Get<string>("userId");

Dispatch Dinâmico

// Útil quando o tipo é descoberto em runtime (ex: mediator genérico)
object request = ResolveRequest(commandName);
object? result = await mediator.Send(request);

object notification = new SomeEvent();
await mediator.Publish(notification);

Estratégias de Publicação

cfg.DefaultPublishStrategy = PublishStrategy.ForeachAwait;       // sequencial (padrão)
cfg.DefaultPublishStrategy = PublishStrategy.WhenAll;            // paralelo, agrega exceções
cfg.DefaultPublishStrategy = PublishStrategy.WhenAllContinueOnError; // paralelo, ignora falhas

TypeEvaluator — Filtrar Assembly Scanning

cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)
   .TypeEvaluator = t => !t.Namespace?.StartsWith("MyApp.Legacy") ?? true;

Source Generator (registro zero-reflection)

O Hermes inclui um Source Generator que escaneia o assembly em compile-time e gera um método AddHermesGenerated() com registro estático de todos os handlers.

Isso elimina completamente o assembly scanning em runtime — startup instantâneo.

O generator é automaticamente incluído ao instalar o pacote Hermes.

Analyzer HRM001

Quando um IRequest não tem handler correspondente, o analyzer emite um aviso em compile-time:

warning HRM001: 'CreateUserCommand' implementa 'IRequest<Unit>' mas não existe
'IRequestHandler<CreateUserCommand, Unit>' neste projeto.

OpenTelemetry

Hermes emite traces e métricas automaticamente. Configure o seu provider:

services.AddOpenTelemetry()
    .WithTracing(b => b.AddSource(HermesDiagnostics.ActivitySourceName))
    .WithMetrics(b => b.AddMeter(HermesDiagnostics.MeterName));

Métricas expostas:

  • hermes.requests.total — total de requests enviados
  • hermes.requests.failed — requests com falha
  • hermes.request.duration — duração em ms (histograma)
  • hermes.notifications.total — notificações publicadas
  • hermes.timeouts.total — requests cancelados por timeout

Hermes.Testing

var fake = new FakeMediator()
    .Setup<GetUserQuery, User>(q => new User(q.Id, "Alice"))
    .Returns<DeleteUserCommand, Unit>(Unit.Value);

// Verificações
Assert.True(fake.WasSent<GetUserQuery>());
Assert.True(fake.WasSent<GetUserQuery>(q => q.Id == 42));
Assert.Equal(1, fake.CountSent<GetUserQuery>());
Assert.True(fake.WasPublished<UserDeleted>());

Licença

MIT

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 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 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