Skip to main content

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>
Library projects

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 triggers AddGenDIServices(). Prefer [Injectable] + [Inject] so the consumer can choose ServiceLifetime, Group, Order, and Key. 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 example AuditBehavior<TMessage, TResponse>) should be registered manually in builder.Services for 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>:

InterfaceHandle return typeDispatch semantics
ICommandHandler<TMessage>ValueTaskAll registered handlers, sequential in registration order
IRequestHandler<TMessage, TResponse>ValueTask<TResponse>Single handler (first registered)
INotificationHandler<TMessage>ValueTaskAll 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)
Unhandled messages

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, ...).

Default routing 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.

NativeAOT

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.