REPR 10.2.1
dotnet add package REPR --version 10.2.1
NuGet\Install-Package REPR -Version 10.2.1
<PackageReference Include="REPR" Version="10.2.1" />
<PackageVersion Include="REPR" Version="10.2.1" />
<PackageReference Include="REPR" />
paket add REPR --version 10.2.1
#r "nuget: REPR, 10.2.1"
#:package REPR@10.2.1
#addin nuget:?package=REPR&version=10.2.1
#tool nuget:?package=REPR&version=10.2.1
REPR
Rest EndPoint Response REPR is a lightweight library that implements the Mediator design pattern in .NET. It helps decouple the sender of a request from its handler, promoting cleaner architecture and separation of concerns. Instead of calling services or logic directly, you send a request (like a command or query), and REPR routes it to the appropriate handler.
REPR optimizes dependency injection life cycles by supporting Transient, Scoped, and Singleton handlers.
Table of Contents
- Use
Use
Initialization
To register REPR handlers, extend desired handler and use AddREPR() to service collection. REPR will find any type with a REPR handler and automatically add that handler to the appropriate life cycle during startup. Only performance hit to your application will be during startup. By default REPR only adds types from the executing assembly. To specify an assembly in the app domain but not in the executing assembly i.e. a common library assembly. You must include the assembly name in the REPROptions.FilterAssemblies AND REPROptions.IncludeAppDomainAssemblies to true.
Add to service collection
services.AddREPR();
REPR Options
public sealed record REPROptions
{
public bool IncludeAppDomainAssemblies { get; set; }
public IEnumerable<string>? FilteredAssemblies { get; set; }
public bool StrictMode { get; set; }
public bool UseSourceGeneration { get; set; }
public ILogger? Logger { get; set; }
public bool Verbose { get; set; }
}
- IncludeAppDomainAssemblies: will use AppDomain.CurrentDomain.GetAssemblies() if set to true and Assembly.GetExecutingAssembly() if set to false. Note: If you set IncludeAppDomainAssemblies to true and do not include a FilteredAssemblies an exception will be thrown.
- FilteredAssemblies: limits the scope of assemblies during search and startup. This is highly recommended for large applications.
- StrictMode: optional and primarily stylistic. Setting this to true will require the handler to be internal and sealed. This is a runtime error, so be careful. One day I would like to add an SCA to this repo.
- UseSourceGeneration: enables compile-time source generation for handler registration, eliminating runtime reflection. See Source Generation for details.
- Logger: optional
ILoggerused by REPR. When set together withVerbose, REPR logs the resolved handler type. See Verbose Logging for details. - Verbose: when true and
Loggeris set, REPR logs the full type name of each resolved handler, job, and subscriber on dispatch.
Verbose Logging
Provide an ILogger and set Verbose to true to have REPR log the full type name of every handler, job, and notification subscriber it resolves at dispatch time. Both options must be set; if either is missing, no logging occurs.
services.AddREPR<Program>(options =>
{
options.Logger = loggerFactory.CreateLogger("REPR");
options.Verbose = true;
});
Each dispatch logs an information-level entry such as:
REPR resolved type: Test.REPR.Library.TransientHandler
For ExecuteJob and Send, every resolved job or subscriber is logged individually.
Source Generation
REPR supports compile-time source generation as an alternative to runtime reflection. This provides:
- Zero runtime reflection - all handler registration is generated at compile time
- Faster startup - no assembly scanning at runtime
- AOT compatibility - works with Native AOT compilation
Enabling Source Generation
The source generator is bundled with the REPR package. To use it:
services.AddREPR<Program>(options =>
{
options.UseSourceGeneration = true;
});
How It Works
When UseSourceGeneration is enabled, the source generator:
- Scans your code at compile time for classes implementing REPR handler interfaces
- Generates a
REPRRegistrationclass with anAddREPRHandlers()method - Groups handlers by lifecycle (Singleton, Scoped, Transient) and sorts them alphabetically
- Registers each handler with the appropriate DI lifetime
Generated Code Example
The generator produces code similar to:
// <auto-generated/>
public static class REPRRegistration
{
public static IServiceCollection AddREPRHandlers(this IServiceCollection services)
{
// Singleton handlers
services.AddSingleton<IRequestHandler<CacheRequest, CacheResponse>, CacheHandler>();
// Scoped handlers
services.AddScoped<IRequestHandler<OrderRequest, OrderResponse>, OrderHandler>();
// Transient handlers
services.AddTransient<IRequestHandler<ValidationRequest, ValidationResponse>, ValidationHandler>();
return services;
}
}
Note: The
IREPRmediator itself is registered byAddREPR()in the REPR library, not by the generated code. Its implementation type isinternalto the REPR assembly, so registering it from generated code (compiled into your assembly) would fail withCS0122.
When to Use Source Generation
| Scenario | Recommendation |
|---|---|
| Native AOT deployment | Use source generation |
| Large applications with many handlers | Use source generation for faster startup |
| Small applications | Either works well |
| Need runtime assembly scanning | Use reflection (default) |
REPR Handlers
REPR handlers default to transient life cycle if base interface is used. To specify a life cycle extend the appropriate interface.
Handlers
- IRequestHandler<TRequest, TResponse>
- ICommandQueryRequestHandler<TCommand, TQuery, TResponse>
- IListRequestHandler<TResponse>
- IJobHandler<TRequest, TResponse>
- INotificationHandler<TRequest>
IRequestHandler
IRequestHandler will search for a single implementation of IRequestHandler<TRequest, TResponse> and execute. REPR will throw an exception if none are found.
- Base (Transient): IRequestHandler<TRequest, TResponse>
- Singleton: ISingletonRequestHandler<TRequest, TResponse>
- Scoped: IScopedRequestHandler<TRequest, TResponse>
- Transient: ITransientRequestHandler<TRequest, TResponse>
Example IRequestHandler Handler
using REPR.Handlers;
namespace Test.REPR.Library;
internal sealed class TransientHandler : ITransientRequestHandler<TransientHandlerRequest, TransientHandlerResponse>
{
public Task<TransientHandlerResponse> Handle(TransientHandlerRequest request, CancellationToken cancellationToken)
{
return Task.FromResult(new TransientHandlerResponse());
}
}
Example GET Controller
private readonly IREPR _repr;
public FooController(IREPR repr)
{
_repr = repr;
}
[HttpGet]
public async Task<IActionResult> Get(TRequest request, CancellationToken cancellationToken)
{
var response = await _repr.Handle<TRequest, TResponse>(request, cancellationToken);
return Ok(response);
}
IListHandler
IListHandler will search for a single implementation of IListRequestHandler<TResponse> and execute. REPR will throw an exception if none are found.
- Base (Transient): IListRequestHandler<TResponse>
- Singleton: ISingletonListRequestHandler<TResponse>
- Scoped: IScopedListRequestHandler<TResponse>
- Transient: ITransientListRequestHandler<TResponse>
Example List Handler
using REPR.Handlers;
namespace Test.REPR.Library.Handlers;
internal sealed class TransientListHandler : ITransientListRequestHandler<TransientListHandlerResponse>
{
public Task<IEnumerable<TransientListHandlerResponse>> Handle(CancellationToken cancellationToken)
{
return Task.FromResult<IEnumerable<TransientListHandlerResponse>>([]);
}
}
Example List (GET) Controller
private readonly IREPR _repr;
public FooController(IREPR repr)
{
_repr = repr;
}
[HttpGet]
public async Task<IActionResult> List(CancellationToken cancellationToken)
{
var response = await _repr.Handle<TResponse>(request, cancellationToken);
return Ok(response);
}
ITransientCommandQueryHandler
ITransientCommandQueryHandler will search for a single implementation of ICommandQueryRequestHandler<TCommand, TQuery, TResponse> and execute. REPR will throw an exception if none are found.
- Base (Transient): ICommandQueryRequestHandler<TCommand, TQuery, TResponse>
- Singleton: ISingletonCommandQueryRequestHandler<TCommand, TQuery, TResponse>
- Scoped: IScopedCommandQueryRequestHandler<TCommand, TQuery, TResponse>
- Transient: ITransientCommandQueryRequestHandler<TCommand, TQuery, TResponse>
Example Command Query Handler
using REPR.Handlers;
namespace Test.REPR.Library;
internal sealed class TransientCommandQueryHandler : ITransientCommandQueryRequestHandler<TransientCommandQueryHandlerCommand, TransientCommandQueryHandlerQuery, TransientCommandQueryHandlerResponse>
{
public Task<TransientCommandQueryHandlerResponse> Handle(TransientCommandQueryHandlerCommand command, TransientCommandQueryHandlerQuery query, CancellationToken cancellationToken)
{
return Task.FromResult(new TransientCommandQueryHandlerResponse());
}
}
Example POST Controller
private readonly IREPR _repr;
public FooController(IREPR repr)
{
_repr = repr;
}
[HttpPost]
public async Task<IActionResult> Post(TCommand command, TQuery query, CancellationToken cancellationToken)
{
var response = await _repr.Handle<TCommand, TQuery, TResponse>(command, query, cancellationToken);
return Ok(response);
}
IJobHandler
IJobHandler will search for a ALL implementation of IJobHandler<TRequest, TResponse> and execute. REPR will throw an exception if none are found.
- Base (Transient): IJobHandler<TRequest, TResponse>
- Singleton: ISingletonJobHandler<TRequest, TResponse>
- Scoped: IScopedJobHandler<TRequest, TResponse>
- Transient: ITransientJobHandler<TRequest, TResponse>
Example Job Handler
using REPR.Handlers;
using Test.REPR.Library.Handlers.Jobs;
namespace Test.REPR.Library;
internal sealed class ScopedJobHandlerOne : IScopedJobHandler<ScopedJobRequest, ScopedJobResponse>
{
public Task<ScopedJobResponse> Execute(ScopedJobRequest request, CancellationToken cancellationToken)
{
return Task.FromResult(new ScopedJobResponse());
}
}
Example POST Jobs Controller
private readonly IREPR _repr;
public FooController(IREPR repr)
{
_repr = repr;
}
[HttpPost]
public async Task<IActionResult> Post(TRequest request, CancellationToken cancellationToken)
{
var response = await _repr.ExecuteJob<TRequest, TResponse>(command, cancellationToken);
return Ok(response);
}
INotificationHandler
IJobHandler will search for a ALL implementation of INotificationHandler<TRequest> and execute. REPR will throw an exception if none are found.
- Base (Transient): INotificationHandler<TRequest>
- Singleton: ISingletonNotificationHandler<TRequest>
- Scoped: IScopedNotificationHandler<TRequest>
- Transient: IScopedNotificationHandler<TRequest>
Example Notification Handler
using REPR.Handlers;
using Test.REPR.Library.Handlers.NotificationRequests;
namespace Test.REPR.Library.Handlers;
internal sealed class TransientNotificationHandlerOne : ITransientNotificationHandler<TransientNotificationRequest>
{
public Task Send(TransientNotificationRequest request, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Example POST Notification Controller
private readonly IREPR _repr;
public FooController(IREPR repr)
{
_repr = repr;
}
[HttpPost]
public async Task<IActionResult> Post(TRequest request, CancellationToken cancellationToken)
{
await _repr.Send<TRequest>(request, cancellationToken);
return Ok();
}
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.