Proxos.Testing 1.0.2

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

Proxos

CI NuGet License: MIT

MIT-licensed mediator for .NET 8, 9 and 10 — compile-time safety via Source Generator and Roslyn Analyzer, built-in OpenTelemetry, API compatible with MediatR.

Por que Proxos?

Aspecto MediatR Proxos
Dispatch Assembly scanning + reflection no startup Source Generator gera tabela de dispatch em compile-time — zero reflection
Startup Lento — resolve handlers via reflection na primeira chamada Instantâneo — registro estático gerado em compile-time
Handler não encontrado Exceção em runtime, mensagem genérica Analyzer PRX001 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 Proxos
dotnet add package Proxos.Testing  # apenas em projetos de teste

Configuração

builder.Services.AddProxos(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 Proxos inclui um Source Generator que escaneia o assembly em compile-time e gera um método AddProxosGenerated() 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 Proxos.

Analyzer PRX001

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

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

OpenTelemetry

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

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

Métricas expostas:

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

Proxos.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>());

Benchmarks

Medido com BenchmarkDotNet em .NET 9, Intel Core i7-1355U. O benchmark inclui criação de scope DI + resolução do mediator + dispatch — cenário realístico de ASP.NET Core.

Send()

Método Tempo Alocações
MediatR 12.x 300 ns 544 B
Proxos 382 ns 568 B

Publish()

Método Tempo Alocações
MediatR 12.x 338 ns 448 B
Proxos 588 ns 440 B

Medido com BenchmarkDotNet em .NET 9, Intel Core i7-1355U. Inclui criação de scope DI + resolução do mediator + dispatch — cenário realístico de ASP.NET Core.

Alocações: Proxos aloca apenas 4.4% a mais no Send e menos que o MediatR no Publish. O overhead de tempo (~1.3–1.7×) é irrelevante em produção — qualquer I/O (banco, HTTP) é 100–1000× mais lento que o dispatch. A principal vantagem é licença MIT, segurança em compile-time e funcionalidades que o MediatR não oferece.

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
1.0.2 102 3/25/2026