PipeR: A Lightweight .NET Pipeline Library for Clean Request Handling
A composable alternative to MediatR — build clean, performant request pipelines in .NET using strongly typed middleware and fluent configuration.
PipeR is a purpose-built middleware-style pipeline library for .NET applications, designed to streamline request/response handling within clean architectures. Inspired by the Mediator pattern (à la MediatR
) but built with clarity, performance, and modularity in mind, PipeR empowers developers to structure business logic as a series of composable steps.
🔍 Why Create PipeR?
PipeR was born from a combination of architectural need, performance considerations, and a shift in the open-source ecosystem surrounding .NET.
Like many .NET developers, I relied heavily on MediatR
for years as the go-to library for decoupling request handling. However, the announcement by its creator Jimmy Bogard that MediatR would be transitioning to a commercial license model sparked reflection — not just about licensing, but about control, transparency, and long-term project autonomy.
This commercialization trend, also affecting libraries like AutoMapper
, is well covered by Milan Jovanovic in his thoughtful commentary: “Open Source in .NET is Changing”.
At the same time, inspiration struck from the community. A video by Mohamad Dbouk titled “Build Your Own Mediator in .NET” laid out a minimalist but clear implementation of the Mediator pattern. This helped crystallize my thinking: I didn’t need to fork MediatR or wait for community reactions — I could build my own approach, one tailored to the specific patterns and performance characteristics I value in my production services.
Thus, PipeR was envisioned — not as a clone, but as a composable, high-performance middleware engine that emphasizes:
- ✅ Explicit flow control through fluent configuration
- ✅ Strong typing for both requests and middleware
- ✅ Lightweight footprint with zero reflection at runtime
- ✅ Plug-and-play middleware and validation support
- ✅ Production resilience without third-party dependency entanglement
PipeR was born out of the desire for a more controlled, pluggable, and high-performance solution — tailored for backends where performance and traceability matter.
🧱 Core Concepts
- Request Types – Define the request + response types (e.g.,
LoginUserRequest : IRequest<LoginResponse>
) - Handlers – Pure handlers containing business logic
- Middleware – Pre/post-processing components injected into the pipeline (e.g., logging, metrics, validation)
- PipelineBuilder – Fluent API to register steps and compose the request execution flow
📦 Basic Usage - PipelineBuilder
var pipeline = new PipelineBuilder()
.Use<LoggingMiddleware>()
.Use<AuthorizationMiddleware>()
.UseHandler<LoginRequestHandler>()
.Build();
var result = await pipeline.ExecuteAsync(new LoginUserRequest { ... });
Each middleware gets a chance to inspect, short-circuit, or transform the request/response. Everything is strongly typed, and you remain in full control of registration order and dependency resolution.
📚 Request/Response Separation (CQRS-Light)
While PipeR focuses heavily on composable middleware pipelines, it also promotes a clean and consistent request/response structure. Inspired by the CQRS pattern, this approach brings clarity and separation of concerns without the complexity of full-blown CQRS implementations.
Here's how simple a controller becomes:
[HttpGet]
public async Task<ActionResult<GetAllBooksResponse>>
GetBooks([FromQuery] GetAllBooksQuery query) =>
await this.HandleRequest(query);
And with the help of a base controller abstraction, you inject your IPiper
dependency once and unify request routing:
public class BookControllerBase : ControllerBase
{
protected readonly IPiper _piper;
public BookControllerBase(IPiper piper)
{
_piper = piper;
}
protected async Task<ActionResult> HandleRequest<TResponse>(IRequest<TResponse> request)
{
var userIdClaim = User.FindFirst("UserId");
if (userIdClaim != null && request is BaseRequest baseRequest)
{
baseRequest.UserId = Guid.Parse(userIdClaim.Value);
return Ok(await _piper.Send(request));
}
return Forbid();
}
}
📝 Requests and Responses
public class GetAllBooksRequest : BaseRequest
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 20;
public string? SearchTerm { get; set; }
}
public class GetAllBooksResponse : BaseResponse
{
public PagedResult<BookDto> Books { get; set; } = new();
}
🔀 Query + Validation Layer
public class GetAllBooksQuery : GetAllBooksRequest, IRequest<GetAllBooksResponse> {}
public class GetAllBooksValidator : AbstractValidator<GetAllBooksQuery>
{
public GetAllBooksValidator()
{
RuleFor(x => x.ApiKey)
.NotEmpty().WithMessage("A valid API Key must be provided!");
RuleFor(x => x.UserId)
.NotNull().WithMessage("User Id must be supplied!");
}
}
⚙️ Abstract Request Handler
Your handlers can inherit from a base class that encapsulates common patterns, enforcing consistency and reducing duplication:
public abstract class RequestHandlerBase<TRequest, TResponse, TValidator>
: IRequestHandler<TRequest, TResponse>
where TRequest : BaseRequest, IRequest<TResponse>
where TResponse : BaseResponse, new()
where TValidator : AbstractValidator<TRequest>, new()
{
// Abstract Handle logic with optional validation
}
This architecture ensures that all requests are routed, validated, and handled consistently — reducing boilerplate and centralizing behavior for cross-cutting concerns.
📊 Advantages Over MediatR
- 🚀 No reflection or dynamic resolution overhead
- 🧩 Middleware chaining using pure DI or fluent configuration
- 💬 Transparent request flow, easier debugging
- 🛡️ Cleaner separation of concerns in complex apps
💡 When to Use PipeR
PipeR excels in systems that:
- Need structured pipelines (like CQRS or request-response)
- Emphasize testability, modularity, and observability
- Run in high-performance or resource-constrained environments (e.g., containers, microservices)
- Desire a MediatR alternative without the baggage
🧪 Example Scenario: Validating and Handling a Login
// Middleware
public class ValidationMiddleware<TRequest, TResponse> : IPipelineMiddleware<TRequest, TResponse>
{
public async Task<TResponse> InvokeAsync(
TRequest request,
Func<TRequest, Task<TResponse>> next)
{
Validate(request); // throw on failure
return await next(request);
}
}
// Handler
public class LoginHandler : IRequestHandler<LoginRequest, LoginResponse>
{
public async Task<LoginResponse> Handle(LoginRequest request)
{
return new LoginResponse { Token = "abc123" };
}
}
🌐 GitHub
The PipeR project will soon be available on GitHub, alongside full documentation and integration examples.
📦 NuGet Plans
The library will be packaged into two NuGet packages:
PipeR.Core
– Core pipeline interfaces and enginePipeR.AspNetCore
– an extension on top of Core for use in ASP.NET Core projects
📚 Coming Soon
- 🔌 MediatR compatibility bridge
- 🎯 Benchmark comparisons
- 📈 OpenTelemetry middleware
- 🧪 Unit testing harnesses for pipelines and middleware
PipeR is your fast track to clean, composable, middleware-first request handling in .NET.
Filed under: .NET, CQRS, Middleware, Clean Architecture, Performance, MediatR Alternative