Migrating from MediatR
Mediant is a drop-in-shaped replacement: IRequest<T>, IRequestHandler<,>, INotification,
IStreamRequest<T> and IPipelineBehavior<,> all exist with the same semantics. You can migrate
incrementally — swap the package first, adopt Result<T> and explicit CQRS types later.
1. Replace the packages
dotnet remove package MediatR
dotnet add package Mediantdotnet add package Mediant.Behaviors # optional: the 11 built-in behaviorsdotnet add package Mediant.FluentValidation # optional: FluentValidation integrationdotnet add package Mediant.AspNetCore # optional: [HttpEndpoint] mapping2. Change the registration
// Beforeservices.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
// Afterservices.AddMediant(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));3. Update the namespaces
// Beforeusing MediatR;
// Afterusing Mediant.Abstractions;using Mediant.Results;4. Handlers return ValueTask
The one required code change. The shape is otherwise identical.
// Before (MediatR)public class CreateOrderHandler : IRequestHandler<CreateOrder, OrderDto>{ public Task<OrderDto> Handle(CreateOrder request, CancellationToken ct) => Task.FromResult(dto);}
// After (Mediant)public class CreateOrderHandler : IRequestHandler<CreateOrder, OrderDto>{ public ValueTask<OrderDto> Handle(CreateOrder request, CancellationToken ct) => new(dto);}Pipeline behaviors change the same way:
public class LoggingBehavior<TReq, TResp> : IPipelineBehavior<TReq, TResp> where TReq : IRequest<TResp>{ public async ValueTask<TResp> Handle(TReq req, RequestHandlerDelegate<TResp> next, CancellationToken ct) => await next();}5. Adopt explicit CQRS (optional, gradual)
IRequest<T> keeps working. Migrate types when you’re ready:
// Beforepublic record CreateOrder(string Name) : IRequest<OrderDto>;
// After — the intent is now in the typepublic record CreateOrder(string Name) : ICommand<Result<OrderDto>>;public record GetOrderById(Guid Id) : IQuery<Result<Order>>;6. Adopt the Result pattern (optional)
Stop throwing for expected failures:
public class CreateOrderHandler : ICommandHandler<CreateOrder, Result<OrderDto>>{ public async ValueTask<Result<OrderDto>> Handle(CreateOrder cmd, CancellationToken ct) { if (await _repo.ExistsAsync(cmd.Name, ct)) return Error.Conflict("Order.Duplicate", "An order with that name exists.");
return Result<OrderDto>.Success(dto); }}See The Result pattern.
7. Turn on the behaviors (optional)
services.AddMediantValidation(typeof(Program).Assembly);services.AddMediantAllBehaviors();8. Map endpoints without controllers (optional)
[HttpEndpoint("POST", "/api/orders")]public record CreateOrder : ICommand<Result<Guid>> { /* ... */ }
app.MapMediantEndpoints(typeof(Program).Assembly);API map
| MediatR | Mediant |
|---|---|
IRequest<T> | IRequest<T>, ICommand<T>, IQuery<T> |
IRequestHandler<TReq, TResp> | IRequestHandler<,>, ICommandHandler<,>, IQueryHandler<,> |
INotification | INotification, IDomainEvent |
INotificationHandler<T> | INotificationHandler<T> |
IStreamRequest<T> | IStreamRequest<T> |
IPipelineBehavior<T, R> | IPipelineBehavior<T, R> (+ IStreamPipelineBehavior) |
Task<T> | ValueTask<T> |
AddMediatR(...) | AddMediant(...) |
| — | Result<T>, Error, ErrorType |
| — | [HttpEndpoint], [Transactional], [Cacheable], … |
What you gain
- MIT license — free for any use.
- Up to 65% faster notification dispatch, 4.7× less memory — see Benchmarks.
- Behaviors ordered explicitly (
IBehaviorOrder), not by registration order. ValidateOnStartupcatches a missing handler at boot, not in production.- Native AOT via a source generator, and built-in OpenTelemetry.