-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Home
A low-ambition library trying to solve a simple problem - decoupling the in-proc sending of messages from handling messages. Cross-platform, supporting .NET 4.5, netstandard1.3
, and netstandard2.0
.
Install via NuGet first:
Install-Package MediatR
MediatR has no dependencies. You will need to configure a single factory delegate, used to instantiate all handlers, pipeline behaviors, and pre/post-processors.
You'll need to configure two dependencies: first, the mediator itself. StructureMap example: cfg.For<IMediator>().Use<Mediator>()
. The other dependency is the factory delegate, ServiceFactory
. The Mediator class is defined as:
public class Mediator : IMediator
{
public Mediator(
ServiceFactory serviceFactory)
The factory delegates are named delegates around a couple of generic factory methods:
public delegate object ServiceFactory(Type serviceType);
StructureMap example: cfg.For<ServiceFactory>().Use<ServiceFactory>(ctx => ctx.GetInstance);
. This factory delegate is how MediatR builds instances of the request and notification handlers.
Finally, you'll need to register your handlers in your container of choice. StructureMap example:
new Container(cfg => cfg.Scan(scanner => {
scanner.TheCallingAssembly();
scanner.AddAllTypesOf(typeof(IRequestHandler<,>));
scanner.AddAllTypesOf(typeof(INotificationHandler<>));
});
Declare whatever flavor of handler you need - sync, async or cancellable async. From the IMediator
side, the interface is async-only, designed for modern hosts.
The full StructureMap example looks like:
var container = new Container(cfg =>
{
cfg.Scan(scanner =>
{
scanner.AssemblyContainingType<Ping>(); // Our assembly with requests & handlers
scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>));
scanner.ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>));
});
cfg.For<ServiceFactory>().Use<ServiceFactory>(ctx => ctx.GetInstance);
cfg.For<IMediator>().Use<Mediator>();
});
The full Autofac example looks like:
// uncomment to enable polymorphic dispatching of requests, but note that
// this will conflict with generic pipeline behaviors
// builder.RegisterSource(new ContravariantRegistrationSource());
// mediator itself
builder
.RegisterType<Mediator>()
.As<IMediator>()
.InstancePerLifetimeScope();
// request & notification handlers
builder.Register<ServiceFactory>(context =>
{
var c = context.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
// finally register our custom code (individually, or via assembly scanning)
// - requests & handlers as transient, i.e. InstancePerDependency()
// - pre/post-processors as scoped/per-request, i.e. InstancePerLifetimeScope()
// - behaviors as transient, i.e. InstancePerDependency()
builder.RegisterAssemblyTypes(typeof(MyType).GetTypeInfo().Assembly).AsImplementedInterfaces(); // via assembly scan
//builder.RegisterType<MyHandler>().AsImplementedInterfaces().InstancePerDependency(); // or individually
If you're using ASP.NET Core then you can skip the above configuration and use MediatR's MediatR.Extensions.Microsoft.DependencyInjection package which includes a IServiceCollection.AddMediatR(Assembly)
extension method, allowing you to register all Handlers and Pre/PostProcessors in a given assembly.
For more examples, check out the samples for working examples using:
- Autofac
- LightInject
- Castle Windsor
- DryIoc
- Ninject
- Simple Injector
- StructureMap
- Lamar
- Unity
These examples highlight all the features of MediatR including sync/async, request/response, pub/sub and more.
MediatR has two kinds of messages it dispatches:
- Request/response messages, dispatched to a single handler
- Notification messages, dispatched to multiple handlers
The request/response interface handles both command and query scenarios. First, create a message:
public class Ping : IRequest<string> { }
Next, create handler:
public class PingHandler : IRequestHandler<Ping, string> {
public Task<string> Handle(Ping request, CancellationToken cancellationToken) {
return Task.FromResult("Pong");
}
}
Finally, send a message through the mediator:
var response = await mediator.Send(new Ping());
Debug.WriteLine(response); // "Pong"
In the case your message does not require a response, use the AsyncRequestHandler<TRequest>
base class:
public class OneWay : IRequest { }
public class OneWayHandlerWithBaseClass : AsyncRequestHandler<OneWay> {
protected override Task Handle(OneWay request, CancellationToken cancellationToken) {
// Twiddle thumbs
}
}
Or if the request is completely synchronous, inherit from the base RequestHandler
class:
public class SyncHandler : RequestHandler<Ping, string> {
protected override string Handle(Ping request) {
return "Pong";
}
}
There are two flavors of requests in MediatR - ones that return a value, and ones that do not:
-
IRequest<T>
- the request returns a value -
IRequest
- the request does not return a value
To simplify the execution pipeline, IRequest
inherits IRequest<Unit>
where Unit
represents a terminal/ignored return type.
Each request type has its own handler interface, as well as some helper base classes:
-
IRequestHandler<T, U>
- implement this and returnTask<U>
-
RequestHandler<T, U>
- inherit this and returnU
Then for requests without return values:
-
IRequestHandler<T>
- implement this and you will returnTask<Unit>
. -
AsyncRequestHandler<T>
- inherit this and you will returnTask
. -
RequestHandler<T>
- inherit this and you will return nothing (void
).
For notifications, first create your notification message:
public class Ping : INotification { }
Next, create zero or more handlers for your notification:
public class Pong1 : INotificationHandler<Ping> {
public Task Handle(Ping notification, CancellationToken cancellationToken) {
Debug.WriteLine("Pong 1");
return Task.CompletedTask;
}
}
public class Pong2 : INotificationHandler<Ping> {
public Task Handle(Ping notification, CancellationToken cancellationToken) {
Debug.WriteLine("Pong 2");
return Task.CompletedTask;
}
}
Finally, publish your message via the mediator:
await mediator.Publish(new Ping());
The default implementation of Publish loops through the notification handlers and awaits each one. This ensures each handler is run after one another.
Depending on your use-case for publishing notifications, you might need a different strategy for handling the notifications. Maybe you want to publish all notifications in parallel, or wrap each notification handler with your own exception handling logic.
A few example implementations can be found in MediatR.Examples.PublishStrategies. This shows four different strategies documented on the PublishStrategy enum.
Handler interfaces are contravariant:
public interface IRequestHandler<in TRequest, TResponse>
where TRequest : IRequest<TResponse> {
Task<TResponse> Handle(TRequest message, CancellationToken cancellationToken);
}
public interface INotificationHandler<in TNotification> {
Task Handle(TNotification notification, CancellationToken cancellationToken);
}
Containers that support generic variance will dispatch accordingly. For example, you can have an INotificationHandler<INotification>
to handle all notifications.
Send/publish are async from the IMediator
side, with corresponding sync and async-based interfaces/base classes for requests/responses/notification handlers.
Your handlers can use the async/await keywords as long as the work is awaitable:
public class PingHandler : IRequestHandler<Ping, Pong> {
public async Task<Pong> Handle(Ping request, CancellationToken cancellationToken) {
await DoPong(); // Whatever DoPong does
}
}
You will also need to register these handlers with your container of your choice, similar to the synchronous handlers shown above.