-
Notifications
You must be signed in to change notification settings - Fork 1
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 and netstandard1.1.
Install via NuGet first:
Install-Package MediatR
MediatR has no dependencies. You will need to configure two factory callbacks, one for building single instances and one for building multiple instances.
You'll need to configure three dependencies: first, the mediator itself. StructureMap example: cfg.For<IMediator>().Use<Mediator>(). The other two dependencies are factory delegates, SingleInstanceFactory and MultiInstanceFactory. The Mediator class is defined as:
public class Mediator : IMediator
{
public Mediator(
SingleInstanceFactory singleInstanceFactory,
MultiInstanceFactory multiInstanceFactory)The factory delegates are named delegates around a couple of generic factory methods:
public delegate object SingleInstanceFactory(Type serviceType);
public delegate IEnumerable<object> MultiInstanceFactory(Type serviceType);StructureMap example: cfg.For<ISingleInstanceFactory>().Use<SingleInstanceFactory>(ctx => t => ctx.GetInstance(t));. These two factory delegates are 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<>)); // Handlers with no response
scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>)); // Handlers with a response
scanner.ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>));
});
cfg.For<SingleInstanceFactory>().Use<SingleInstanceFactory>(ctx => t => ctx.GetInstance(t));
cfg.For<MultiInstanceFactory>().Use<MultiInstanceFactory>(ctx => t => ctx.GetAllInstances(t));
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 handlers
builder
.Register<SingleInstanceFactory>(ctx => {
var c = ctx.Resolve<IComponentContext>();
return t => { object o; return c.TryResolve(t, out o) ? o : null; };
})
.InstancePerLifetimeScope();
// notification handlers
builder
.Register<MultiInstanceFactory>(ctx => {
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>)c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
})
.InstancePerLifetimeScope();
// 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 individuallyIf 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
- 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 IRequestHandler<TRequest> interface:
public class OneWay : IRequest { }
public class OneWayHandlerWithBaseClass : IRequestHandler<OneWay> {
public Task Handle(OneWay request, CancellationToken cancellationToken) {
// Twiddle thumbs
}
}For convenience of handlers that do not need the use of the cancellation token, inherit from the AsyncRequestHandler base class:
public class AsyncNoCancellation : AsyncRequestHandler<Ping, string> {
protected override Task<string> Handle(Ping request) {
return Task.FromResult("Pong");
}
}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";
}
}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());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.