A lightweight, flexible mediator library for .NET applications β designed with Clean Architecture in mind.
PassR is a minimal, fast, and extensible .NET library that enables clean separation of concerns using the Mediator pattern.
Inspired by MediatR, PassR adds support for:
- β
Request/response handling (
IRequest,IRequestHandler) - β
Fire-and-forget notifications (
INotification,INotificationHandler) - β
Middleware pipeline behaviors (
IPipelineBehavior) - β Clean and testable architecture, built on top of dependency injection
- β
Command & Query separation via
ICommand,IQueryabstractions
dotnet add package Verbytes.PassRRegister PassR and presentation services in your Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddPresentation();
builder.Services.AddPassR(options =>
{
options.RegisterServicesFromAssembly(typeof(CreateUserCommandHandler).Assembly);
options.AddOpenBehavior(typeof(LoggingBehavior<,>));
});
builder.Services.AddEndpoints(Assembly.GetExecutingAssembly());You can set up the full API versioning + exception handler + Swagger pipeline with a single method:
var app = builder.Build();
app.UsePassRPresentation(endpointAssembly: typeof(IEndpoint).Assembly);This configures:
- API versioning using route segments (
/api/v1/...) - Swagger UI with support for multiple versions
- Automatic endpoint discovery via
IEndpointimplementations - Custom exception middleware with problem response output
public record GetUserQuery(Guid UserId) : IQuery<UserDto>;public class GetUserQueryHandler : IQueryHandler<GetUserQuery, UserDto>
{
public async ValueTask<Result<UserDto>> HandleAsync(GetUserQuery request, CancellationToken cancellationToken)
{
// simulate user retrieval
return Result.Success(new UserDto(request.UserId, "burak@example.com"));
}
}var result = await mediator.SendAsync(new GetUserQuery(Guid.NewGuid()));public record UserCreated(Guid UserId) : INotification;
public class SendWelcomeEmailHandler : INotificationHandler<UserCreated>
{
public ValueTask HandleAsync(UserCreated notification, CancellationToken cancellationToken)
{
Console.WriteLine($"Sending email to user: {notification.UserId}");
return ValueTask.CompletedTask;
}
}Publish with:
await mediator.PublishAsync(new UserCreated(userId));public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async ValueTask<TResponse> HandleAsync(
TRequest request,
CancellationToken cancellationToken,
RequestHandlerDelegate<TResponse> next)
{
Console.WriteLine($"[START] {typeof(TRequest).Name}");
var response = await next();
Console.WriteLine($"[END] {typeof(TRequest).Name}");
return response;
}
}PassR now supports automatic API versioning for Minimal APIs with seamless Swagger integration.
- Annotate your endpoint class with
[ApiVersion(x)](e.g.[ApiVersion(2)]) - PassR dynamically detects all versions (v1, v2, v3...) without hardcoding
- Endpoints are grouped under
/api/v{version}automatically - Swagger UI displays version tabs for each registered version
[ApiVersion(2)]
public class PostTestV2 : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder app)
{
app.MapPost("PostTest", ...)
.WithTags("Tests")
}
}No need to configure versions manually. Just use:
var app = builder.Build();
app.UsePassRPresentation(Assembly.GetExecutingAssembly());Enjoy fully dynamic, scalable API versioning with clean Swagger support.
| Interface | Purpose |
|---|---|
IRequest<TResponse> |
Represents a request with a response |
IRequestHandler<,> |
Handles a request |
INotification |
Represents a fire-and-forget event |
INotificationHandler<> |
Handles a notification |
IPipelineBehavior<,> |
Middleware logic around requests |
ICommand, IQuery |
CQRS-style abstractions |
Result, Error, ValidationError |
Functional result pattern |
Check out /examples/WebAPI for how to:
- Wire up
PassRinto a minimal API project - Use commands, queries, notifications
- Add pipeline logging
- Validate using a behavior layer
MIT Β© Burak Besli