Proxos 1.0.2

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

Showing the top 1 NuGet packages that depend on Proxos:

Package Downloads
Proxos.Testing

Testing utilities for Proxos — FakeMediator with fluent setup and assertion API

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 105 3/25/2026