Installation, Configuration & Usage
This page centralizes installation, configuration, and usage details for each NetMediate package.
Core package (NetMediate)
Installation
dotnet add package NetMediate.Core
dotnet add package NetMediate.SourceGeneration
For direct usage, this is enough:
<PackageReference Include="NetMediate.Core" Version="x.x.x" />
<PackageReference Include="NetMediate.SourceGeneration" Version="x.x.x.x">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>contentfiles; compile; runtime</PrivateAssets>
</PackageReference>
Use NetMediate.SourceGeneration with the explicit analyzer-style metadata shown above.
Configuration
Handler registration is done automatically at compile time via NetMediate.SourceGeneration. Call the generated method:
using GenDI;
using NetMediate;
// Source generation discovers all ICommandHandler<>, IRequestHandler<,>,
// INotificationHandler<>, and IStreamHandler<,> implementations in your project
// and generates closed-type AOT-safe registrations automatically.
builder.Services.AddNetMediate();
GenDI-first style:
AddNetMediate()also triggersAddGenDIServices(). Prefer[Injectable]+[Inject]so the consumer can chooseServiceLifetime,Group,Order, andKey. Use[Injectable<TService>]only when you need to force a specific non-generic contract and contract discovery does not already find[ServiceInjection]. Concrete non-generic classes that implement closed generic contracts can still use[Injectable]. Only generic/open service implementations (for exampleAuditBehavior<TMessage, TResponse>) should be registered manually inbuilder.Servicesfor the AOT-oriented path.
Usage
// Command: dispatched sequentially to all registered handlers, no return value
await mediator.SendCreateUserCommandAsync(new CreateUserCommand("user-1"), cancellationToken);
// Request: single handler, returns a response
var dto = await mediator.RequestGetUserRequestAsync(new GetUserRequest("user-1"), cancellationToken);
// Notification: all handlers started in parallel (fire-and-forget); handler exceptions discarded by executor
await mediator.NotifyUserCreatedNotificationAsync(new UserCreatedNotification("user-1"), cancellationToken);
// Notification (batch): each message's pipeline dispatched in parallel (Task.WhenAll across messages)
mediator.NotifyUserCreatedNotification(new[] { n1, n2, n3 }, cancellationToken);
// Stream: single handler; yields items asynchronously
await foreach (var item in mediator.StreamGetEventsQueryAsync(new GetEventsQuery(), cancellationToken))
Console.WriteLine(item);
Message types
No marker interfaces are required. Any plain class or record can be a message:
public record CreateUserCommand(string Email); // command
public record GetUserRequest(string UserId); // request
public record UserCreatedNotification(string UserId); // notification
public record GetEventsQuery(int MaxItems); // stream request
The optional IMessage marker interface is available if you want to constrain message types in your own abstractions.
Handler return types and dispatch semantics
All handler Handle methods return ValueTask or ValueTask<TResponse>:
| Interface | Handle return type | Dispatch semantics |
|---|---|---|
ICommandHandler<TMessage> | ValueTask | All registered handlers, sequential in registration order |
IRequestHandler<TMessage, TResponse> | ValueTask<TResponse> | Single handler (first registered) |
INotificationHandler<TMessage> | ValueTask | All handlers started in parallel (fire-and-forget via Task.WhenAll); handler exceptions discarded |
IStreamHandler<TMessage, TResponse> | IAsyncEnumerable<TResponse> | All registered handlers, items merged sequentially (handler A items first, then handler B) |
Send and Notify are silent no-ops when no handler is registered. Request and RequestStream throw InvalidOperationException.
Keyed handler registration
Use GenDI metadata to register multiple handlers for the same message type under distinct keys and dispatch to a specific one at runtime:
[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 1)]
public sealed class DefaultCommandHandler : ICommandHandler<MyCommand> { }
[Injectable(ServiceLifetime.Scoped, Group = 100, Order = 2, Key = "audit")]
public sealed class AuditCommandHandler : ICommandHandler<MyCommand> { }
// Dispatch to the default (null-key) handlers
await mediator.SendMyCommandAsync(command, ct);
// Dispatch only to handlers registered under "audit"
await mediator.SendMyCommandAsync("audit", command, ct);
The same key parameter is available on all dispatch methods: Send(key, ...), Notify(key, ...), Request(key, ...), and RequestStream(key, ...).
A null key flows through the pipeline unchanged. This means mediator.SendMyCommandAsync(command, ct) and mediator.SendMyCommandAsync(null, command, ct) are exactly equivalent and target the non-keyed handlers registered in the container.
Non-keyed registration and dispatch remain fully NativeAOT-compatible. Keyed registration uses IKeyedServiceProvider internally, which is not NativeAOT-compatible; use it only when NativeAOT is not required.
Pipeline behaviors
Configuration
Use static decorators with DecoratorForAttribute.
[DecoratorFor<IRequestHandler<MyRequest, MyResponse>>(Order = 1)]
public sealed class MyLoggingDecorator(IRequestHandler<MyRequest, MyResponse> inner)
: IRequestHandler<MyRequest, MyResponse>
{
public Task<MyResponse> Handle(
MyRequest message,
CancellationToken cancellationToken = default) =>
inner.Handle(message, cancellationToken);
}
builder.Services.AddNetMediate();
Source generation (NetMediate.SourceGeneration)
Installation
NetMediate.SourceGeneration is installed directly in the startup/application project. Its buildTransitive file adds NetMediate and GenDI.SourceGenerator automatically.
Usage
builder.Services.AddNetMediate();
The generator discovers all ICommandHandler<>, IRequestHandler<,>, INotificationHandler<>, and IStreamHandler<,> implementations in your project and emits strongly-typed closed-type registrations — no reflection, fully AOT-compatible. See the Source Generation guide.
Moq (NetMediate.Moq)
Installation
dotnet add package NetMediate.Moq
Usage
using NetMediate.Moq;
// Create and register a mediator mock
var mediatorMock = services.AddMediatorMock();
mediatorMock.Setup(m => m.Send(It.IsAny<MyCommand>(), It.IsAny<CancellationToken>()))
.ReturnsCompletedTask();
// Replace any service with a singleton mock
var clockMock = services.AddMockSingleton<IClock>();
See the Moq Recipes guide for full details.