-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Home
MediatR is a low-ambition library trying to solve a simple problem — decoupling the in-process sending of messages from handling messages. Cross-platform, supporting .NET Framework 4.6.1 and netstandard2.0
.
Install the package 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 will need to configure two dependencies: first, the mediator itself. 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);
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.
Finally, you'll need to register your handlers in your container of choice.
If you're using ASP.NET Core then you can skip the 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/post-processors in a given assembly.
Setup Mediator itself:
cfg.For<IMediator>().Use<Mediator>()
.
Setup the factory delegate:
cfg.For<ServiceFactory>().Use<ServiceFactory>(ctx => ctx.GetInstance);
. This factory delegate is how MediatR builds instances of the request and notification handlers.
new Container(cfg => cfg.Scan(scanner => {
scanner.TheCallingAssembly();
scanner.AddAllTypesOf(typeof(IRequestHandler<,>));
scanner.AddAllTypesOf(typeof(INotificationHandler<>));
});
The full 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 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
For more examples, check out the samples for working examples using:
- Autofac
- Castle Windsor
- DryIoc
- Lamar
- LightInject
- Ninject
- Simple Injector
- StructureMap
- 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 a 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 asynchronous from the IMediator
side, with corresponding synchronous and asynchronous-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 the IoC container of your choice, similar to the synchronous handlers shown above.