源码相关
MediaR是一个中介者库,里面实现了请求响应/发布订阅两种模式。
内部相当于一个容器,由源码可知,内部创建时注入了一个容器委托(从容器中获得对应的实现接口的实例)。
// 这里注入了一个委托,用于是从服务容器中获取对应项实例。
public Mediator(ServiceFactory serviceFactory)
{
_serviceFactory = serviceFactory;
}
// 这一段有两个方法,一个是获得一个实例,一个是获得所有实现了这个接口的实例,这个是给发布订阅用的。
public delegate object ServiceFactory(Type serviceType);
public static class ServiceFactoryExtensions
{
public static T GetInstance<T>(this ServiceFactory factory)
=> (T) factory(typeof(T));
public static IEnumerable<T> GetInstances<T>(this ServiceFactory factory)
=> (IEnumerable<T>) factory(typeof(IEnumerable<T>));
}
获得所有的订阅者后,将在Mediator的方法Publish依次调用,如果途中取消,可以使用CancellationToken进行标记。
protected virtual async Task PublishCore(IEnumerable<Func<INotification, CancellationToken, Task>> allHandlers, INotification notification, CancellationToken cancellationToken)
{
foreach (var handler in allHandlers)
{
await handler(notification, cancellationToken).ConfigureAwait(false);
}
}
发布订阅排序问题
问题:NOP中可以有顺序,那么问题来了,从上面看来,MediatR并没有给我们对订阅者排序的选择。那么我们如何才能对事件处理者排序呢?
目前有两种方式,第一种是修改MediatR的 源码,添加特性
namespace MediatR.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class EventOrderAttribute : Attribute
{
/// <summary>
/// 排序值。
/// </summary>
public int Order { get; set; }
}
}
总所周知,依赖注入一个接口多个实现的时候,可以通过IEnumerable获得所有的实现。
因此,我们可以在获取势力的时候,即上面的方法ServiceFactoryExtensions.IEnumerable<T> GetInstances<T>(this ServiceFactory factory)中处理
如下:
public static class ServiceFactoryExtensions
{
public static T GetInstance<T>(this ServiceFactory factory)
=> (T)factory(typeof(T));
public static IEnumerable<T> GetInstances<T>(this ServiceFactory factory)
{
var instanceList = (IEnumerable<T>)factory(typeof(IEnumerable<T>));
// 反射进行排序。
if (instanceList.Any(o => o.GetType().GetCustomAttributes().Any(o => o.GetType().Equals(typeof(EventOrderAttribute)))))
instanceList = instanceList
.OrderBy(o => o.GetType().GetCustomAttribute<EventOrderAttribute>() == null ? 0 : o.GetType().GetCustomAttribute<EventOrderAttribute>().Order).ToList();
return instanceList;
}
当然,以上方案是不推荐的,毕竟修改了核心源码,但我们依然可以利用容器的特性,安装一定顺序进行排序,然后依次注入,即可实现上面一样的效果。
首先依然保留原来的特性,这次我们把魔手伸向
MediatR.Extensions.Microsoft.DependencyInjection 这个项目中,一下只是为了快速参考,所以直接改这个,你也可以自己实现一个。
在这个项目中,主要的是ServiceRegistrar这个类,进行注册,里面是以此注入不同的接口。
关注:ServiceRegistrar.ConnectImplementationsToTypesClosing方法
private static void ConnectImplementationsToTypesClosing(Type openRequestInterface,
IServiceCollection services,
IEnumerable<Assembly> assembliesToScan,
bool addIfAlreadyExists)
{
// 这个集合收集中保存的是处理者。
var concretions = new List<Type>();
// 这个集合保存的是处理者的接口。
var interfaces = new List<Type>();
foreach (var type in assembliesToScan.SelectMany(a => a.DefinedTypes).Where(t => !t.IsOpenGeneric()))
{
var interfaceTypes = type.FindInterfacesThatClose(openRequestInterface).ToArray();
if (!interfaceTypes.Any()) continue;
if (type.IsConcrete())
{
concretions.Add(type);
}
foreach (var interfaceType in interfaceTypes)
{
interfaces.Fill(interfaceType);
}
}
// 这一部分是将接口与相应的实例者相匹配,然后注册到容器中。
foreach (var @interface in interfaces)
{
var exactMatches = concretions.Where(x => x.CanBeCastTo(@interface)).ToList();
if (addIfAlreadyExists)
{
foreach (var type in exactMatches)
{
services.AddTransient(@interface, type);
}
}
else
{
if (exactMatches.Count > 1)
{
exactMatches.RemoveAll(m => !IsMatchingWithInterface(m, @interface));
}
// 针对一个接口的注册,注入多个矗立着。
foreach (var type in exactMatches)
{
services.TryAddTransient(@interface, type);
}
}
if (!@interface.IsOpenGeneric())
{
AddConcretionsThatCouldBeClosed(@interface, concretions, services);
}
}
}
那么我们也只需要按照这种形式,先排序在一次注入到容器中。
如下:
private static void ConnectImplementationsToTypesClosing(Type openRequestInterface,
IServiceCollection services,
IEnumerable<Assembly> assembliesToScan,
bool addIfAlreadyExists)
{
// 这个集合收集中保存的是处理者。
var concretions = new List<Type>();
// 新增一个用于收集排序的集合。
var concretionsOrder = new List<Type>();
// 这个集合保存的是处理者的接口。
var interfaces = new List<Type>();
foreach (var type in assembliesToScan.SelectMany(a => a.DefinedTypes).Where(t => !t.IsOpenGeneric()))
{
var interfaceTypes = type.FindInterfacesThatClose(openRequestInterface).ToArray();
if (!interfaceTypes.Any()) continue;
if (type.IsConcrete())
{
if (type.GetCustomAttributes().Any(o => o.GetType().Equals(typeof(EventOrderAttribute))))
concretionsOrder.Add(type);
else
concretions.Add(type);
}
foreach (var interfaceType in interfaceTypes)
{
interfaces.Fill(interfaceType);
}
}
// 排序后加入到服务中。
concretionsOrder = OrderService(concretionsOrder);
AddService(concretions);
AddService(concretionsOrder);
// 排序服务。
List<Type> OrderService(List<Type> typeList)
{
if (typeList.Any())
return typeList.OrderBy(o => o.GetCustomAttribute<EventOrderAttribute>().Order).ToList();
else
return typeList;
}
// 添加服务。
void AddService(List<Type> typeList)
{
// 这一部分是将接口与相应的实例者相匹配,然后注册到容器中。
foreach (var @interface in interfaces)
{
var exactMatches = typeList.Where(x => x.CanBeCastTo(@interface)).ToList();
if (addIfAlreadyExists)
{
foreach (var type in exactMatches)
{
services.AddTransient(@interface, type);
}
}
else
{
if (exactMatches.Count > 1)
{
exactMatches.RemoveAll(m => !IsMatchingWithInterface(m, @interface));
}
// 针对一个接口的注册,注入多个矗立着。
foreach (var type in exactMatches)
{
services.TryAddTransient(@interface, type);
}
}
if (!@interface.IsOpenGeneric())
{
AddConcretionsThatCouldBeClosed(@interface, typeList, services);
}
}
}
}
下面是ABC对应321.
[EventOrder(Order = 3)]
public class AClHandler : INotificationHandler<EventData>
{
public Task Handle(EventData notification, CancellationToken cancellationToken)
{
Console.WriteLine(nameof(AClHandler));
return Task.CompletedTask;
}
}
[EventOrder(Order = 2)]
public class BClHandler : INotificationHandler<EventData>
{
public Task Handle(EventData notification, CancellationToken cancellationToken)
{
Console.WriteLine(nameof(BClHandler));
return Task.CompletedTask;
}
}
[EventOrder(Order = 1)]
public class CClHandler : INotificationHandler<EventData>
{
public Task Handle(EventData notification, CancellationToken cancellationToken)
{
Console.WriteLine(nameof(CClHandler));
return Task.CompletedTask;
}
}
看看结果:
如果去掉排序
第二种先排序再插入容器的方法,或许不适用所有的依赖注入容器,但是原生容器时没有问题的,Autofac应该也没有问题。