C#写一个简单的EventBus事件总线
前言:
因为我自己写的一个WPF前后端系统,会涉及到接收某个消息之后,针对这个动作会执行一些其他的数据操作等,为了程序的解耦,写了个简单的事件总线来管理。
插播一条广告:
Gitee仓库:Vampirewal.Admin欢迎大家Start
该事件总线我也打包成Nuget包了,地址:NuGet\Install-Package Vampirewal.Core.EventBus -Version 1.0.0.9
关于事件总线EventBus
网上关于这块的内容太多了,我就不再重复赘述,如需了解详细,请自行搜索一下其他博客的内容。
进入正文
1、代码
直接放出全部代码
文件名称 | 功能描述 |
---|---|
IVampirewalCoreEventBusFactory | VampirewalCore事件总线工厂接口 |
VampirewalCoreEventBusFactory | VampirewalCore事件总线工厂接口实现 |
IVampirewalCoreEventFactory | 事件工厂接口 |
EventFactoryContext | 事件工厂上下文 |
EventContext | 事件上下文 |
VampirewalCoreEventBusException | VampirewalCore事件总线异常类 |
EventBusExcuteAttribute | 标记在事件总线中的方法才会执行 |
EventBusAOPAttribute | 事件工厂AOP特性,需继承 |
VampirewalCoreEventBusExtension | 扩展方法 |
1.1、VampirewalCore事件总线工厂接口
/// <summary>
/// VampirewalCore事件总线工厂接口
/// </summary>
public interface IVampirewalCoreEventBusFactory
{
/// <summary>
/// 推送
/// </summary>
/// <param name="EventFactoryName">事件工厂Type名称</param>
/// <param name="Params"></param>
void Publish(string EventFactoryName, object? Params = null);
/// <summary>
/// 推送
/// </summary>
/// <typeparam name="TEventFactory"></typeparam>
/// <param name="Params"></param>
void Publish<TEventFactory>(object? Params = null) where TEventFactory : IVampirewalCoreEventFactory;
/// <summary>
/// 并行推送
/// </summary>
/// <param name="EventFactoryName"></param>
/// <param name="Params"></param>
void PublishParallel(string EventFactoryName, object? Params = null);
/// <summary>
/// 并行推送
/// </summary>
/// <typeparam name="TEventFactory"></typeparam>
/// <param name="Params"></param>
void PublishParallel<TEventFactory>(object? Params = null) where TEventFactory : IVampirewalCoreEventFactory;
}
1.2、VampirewalCore事件总线工厂接口实现
/// <summary>
/// VampirewalCore事件总线工厂
/// </summary>
internal class VampirewalCoreEventBusFactory : IVampirewalCoreEventBusFactory
{
private readonly IServiceProvider Service;
private Dictionary<string, Type> EventBusFactoryTypes { get; set; }
public VampirewalCoreEventBusFactory(IServiceProvider provider)
{
Service = provider;
EventBusFactoryTypes = new Dictionary<string, Type>();
RegisterAll();
}
/// <summary>
/// 注册全部
/// </summary>
private void RegisterAll()
{
Type[]? types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(t => t.GetInterfaces().Contains(typeof(IVampirewalCoreEventFactory))).ToArray();
foreach (Type type in types)
{
if (type != null)
{
EventBusFactoryTypes.Add(type.Name, type);
}
}
}
public void Publish(string EventFactoryName, object? Params = null)
{
if (EventBusFactoryTypes.TryGetValue(EventFactoryName, out Type type))
{
var im = Service.GetService(type) as IVampirewalCoreEventFactory;
if (im == null)
throw new VampirewalCoreEventBusException($"未找到类型为{type}的事件工厂!");
//获取当前事件工厂内的所有标记了EventBusExcuteAttribute的方法
var methods = type.GetMethods().Where(w => w.GetCustomAttribute<EventBusExcuteAttribute>() != null).ToList();
if (methods != null && methods.Count() == 0)
{
throw new VampirewalCoreEventBusException($"该事件工厂中没有找到标记EventBusExcuteAttribute特性的方法!");
}
else if (methods != null)
{
methods.Sort((s1, s2) =>
{
var attr1 = s1.GetCustomAttribute<EventBusExcuteAttribute>();
var attr2 = s2.GetCustomAttribute<EventBusExcuteAttribute>();
if (attr1.ExcuteSort > attr2.ExcuteSort)
{
return 1;
}
else
{
return -1;
}
});
//new一个事件工厂上下文
EventFactoryContext FactoryContexts = new EventFactoryContext();
foreach (var sub in methods)
{
//new一个事件上下文,方便传递数据
EventContext context = new EventContext()
{
PassData = Params,
MethodName = sub.Name,
ExcutingTime = DateTime.Now
};
//将事件上下文添加到事件工厂的上下文集合中
FactoryContexts.EventContexts.Add(context);
//获取到当前方法上的AOP特性
var attrs = sub.GetCustomAttributes().Where(w => w is EventBusAOPAttribute).ToList();
try
{
//循环执行AOP特性的执行前方法
foreach (EventBusAOPAttribute item in attrs)
{
item.ExcuteBefore(context);
}
sub.Invoke(im, new object[] { context });
}
catch (Exception ex)
{
//捕获方法执行中的异常信息
context.ExceptionInfo = ex.InnerException;
//将事件工厂的是否全部完成成功标记为false
FactoryContexts.IsEventFactoryComplateSuccess = false;
}
finally
{
context.ExcutedTime = DateTime.Now;
//循环执行AOP特性的执行后方法
foreach (EventBusAOPAttribute item in attrs)
{
item.ExcuteAfter(context);
}
Params = context.PassData;
}
}
//在事件工厂内的方法全部都执行完成之后,执行事件工厂的完成方法
im.EventFactoryComplate(FactoryContexts);
}
}
else
{
throw new VampirewalCoreEventBusException($"未找到{EventFactoryName}对应的事件工厂!");
}
}
public void Publish<TEventFactory>(object? Params = null) where TEventFactory : IVampirewalCoreEventFactory
{
Type type = typeof(TEventFactory);
Publish(type.Name, Params);
}
public void PublishParallel(string EventFactoryName, object? Params = null)
{
if (EventBusFactoryTypes.TryGetValue(EventFactoryName, out Type type))
{
var im = Service.GetService(type) as IVampirewalCoreEventFactory;
if (im == null)
throw new VampirewalCoreEventBusException($"未找到类型为{type}的事件工厂!");
//获取当前事件工厂内的所有标记了EventBusExcuteAttribute的方法
var methods = type.GetMethods().Where(w => w.GetCustomAttribute<EventBusExcuteAttribute>() != null).ToList();
if (methods != null && methods.Count() == 0)
{
throw new VampirewalCoreEventBusException($"该事件工厂中没有找到标记EventBusExcuteAttribute特性的方法!");
}
else if (methods != null)
{
//new一个事件工厂上下文
EventFactoryContext FactoryContexts = new EventFactoryContext();
//这个foreach方法会并行执行,且会等待所有的方法执行完之后,再往下执行方法
var result = Parallel.ForEach(methods, sub =>
{
//new一个事件上下文,方便传递数据
EventContext context = new EventContext()
{
PassData = Params,
MethodName = sub.Name,
ExcutingTime = DateTime.Now
};
//将事件上下文添加到事件工厂的上下文集合中
FactoryContexts.EventContexts.Add(context);
//获取到当前方法上的AOP特性
var attrs = sub.GetCustomAttributes().Where(w => w is EventBusAOPAttribute).ToList();
try
{
//循环执行AOP特性的执行前方法
foreach (EventBusAOPAttribute item in attrs)
{
item.ExcuteBefore(context);
}
sub.Invoke(im, new object[] { context });
}
catch (Exception ex)
{
//捕获方法执行中的异常信息
context.ExceptionInfo = ex.InnerException;
//将事件工厂的是否全部完成成功标记为false
FactoryContexts.IsEventFactoryComplateSuccess = false;
}
finally
{
context.ExcutedTime = DateTime.Now;
//循环执行AOP特性的执行后方法
foreach (EventBusAOPAttribute item in attrs)
{
item.ExcuteAfter(context);
}
}
});
if (result.IsCompleted)
{
//在事件工厂内的方法全部都执行完成之后,执行事件工厂的完成方法
im.EventFactoryComplate(FactoryContexts);
}
}
}
else
{
throw new VampirewalCoreEventBusException($"未找到{EventFactoryName}对应的事件工厂!");
}
}
public void PublishParallel<TEventFactory>(object? Params = null) where TEventFactory : IVampirewalCoreEventFactory
{
Type type = typeof(TEventFactory);
PublishParallel(type.Name, Params);
}
}
1.3、事件工厂接口
/// <summary>
/// 事件工厂
/// </summary>
public interface IVampirewalCoreEventFactory
{
/// <summary>
/// 工厂名称(方便查找定位)
/// </summary>
string FactoryName { get; }
/// <summary>
/// 事件工厂完成
/// </summary>
/// <param name="context"></param>
void EventFactoryComplate(EventFactoryContext context);
}
1.4、事件工厂上下文
// <summary>
/// 事件工厂上下文
/// </summary>
public class EventFactoryContext
{
public EventFactoryContext()
{
EventContexts = new List<EventContext>();
IsEventFactoryComplateSuccess=true;
}
/// <summary>
/// 内部方法上下文
/// </summary>
public List<EventContext> EventContexts { get;internal set; }
/// <summary>
/// 事件工厂是否全部完成成功
/// </summary>
public bool IsEventFactoryComplateSuccess { get;internal set; }
}
1.5、事件上下文
/// <summary>
/// 事件上下文
/// </summary>
public class EventContext
{
/// <summary>
/// 传递过来的参数
/// </summary>
public object? PassData { get; set; }
/// <summary>
/// 执行的方法名
/// </summary>
public string MethodName { get; internal set; } = "";
/// <summary>
/// 开始执行时间
/// </summary>
public DateTime? ExcutingTime { get; internal set; }
/// <summary>
/// 执行结束时间
/// </summary>
public DateTime? ExcutedTime { get; internal set; }
/// <summary>
/// 异常信息
/// </summary>
public Exception? ExceptionInfo { get; internal set; }
}
1.6、VampirewalCore事件总线异常类
/// <summary>
/// VampirewalCore事件总线异常类
/// </summary>
internal class VampirewalCoreEventBusException : Exception
{
/// <summary>
/// VampirewalCore事件总线异常类
/// </summary>
/// <param name="ErrorMsg">异常信息</param>
public VampirewalCoreEventBusException(string ErrorMsg) : base(ErrorMsg)
{
}
}
1.7、EventBusExcuteAttribute,标记在事件总线中的方法才会执行
/// <summary>
/// 标记在事件总线中的方法才会执行
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class EventBusExcuteAttribute : Attribute
{
/// <summary>
/// 执行顺序
/// </summary>
public int ExcuteSort { get; set; } = 99;
}
1.8、EventBusAOPAttribute,事件工厂AOP特性,需继承
/// <summary>
/// 事件工厂AOP特性,需继承
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public abstract class EventBusAOPAttribute : Attribute
{
/// <summary>
/// 方法执行前
/// </summary>
/// <param name="context">上下文</param>
public abstract void ExcuteBefore(EventContext context);
/// <summary>
/// 方法执行后
/// </summary>
/// <param name="context">上下文</param>
public abstract void ExcuteAfter(EventContext context);
}
1.9、VampirewalCoreEventBusExtension,扩展
/// <summary>
///
/// </summary>
public static class VampirewalCoreEventBusExtension
{
/// <summary>
/// 将事件总线工厂注册到IoC中
/// </summary>
/// <param name="services"></param>
public static void AddVampirewalCoreEventBusFactory(this IServiceCollection services)
{
Type[]? types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(t => t.GetInterfaces().Contains(typeof(IVampirewalCoreEventFactory))).ToArray();
AddVampirewalCoreEventBusFactory(services, factory =>
{
foreach (var type in types)
{
factory.Add(type);
}
});
}
/// <summary>
/// 如果事件工厂放在其他项目内,可能自动全部加载的方式无法获取到,可通过这个方法加载
/// </summary>
/// <param name="services"></param>
/// <param name="eventFactorys"></param>
public static void AddVampirewalCoreEventBusFactory(this IServiceCollection services, Action<List<Type>> eventFactorys)
{
List<Type> types = new List<Type>();
eventFactorys?.Invoke(types);
foreach (Type type in types)
{
if (type != null)
{
//var methods = type.GetMethods(BindingFlags.Public);
var ErrorMethods = type.GetMethods()
.Where(w => w.GetCustomAttribute<EventBusExcuteAttribute>() != null && (w.GetParameters().Length != 1 || w.GetParameters()[0].ParameterType != typeof(EventContext)))
.Select(s => $"{type.Name}事件工厂内的{s.Name}方法参数数量不等于1或参数类型不是EventContext!请修改!")
.ToList();
if (ErrorMethods != null && ErrorMethods.Count > 0)
{
throw new VampirewalCoreEventBusException(string.Join("\r\n", ErrorMethods));
}
//注册事件工厂
services.AddSingleton(type);
}
}
//注册事件总线工厂
services.AddSingleton<IVampirewalCoreEventBusFactory, VampirewalCoreEventBusFactory>();
}
}
2、如何使用
2.1、将EventBusFactory注册进IServiceCollection
因为在上文的代码中,已经将我们的EventBusFactory使用单例注册进了IServiceCollection,之后在使用的时候,就可以通过构造函数注入的方式,直接在其他模块使用
2.1.1 注册进Vampirewal.Admin中
public class TestBootStartUp : VampirewalBootStartUp
{
protected override string FirstViewKey => "MainView";
protected override void RegisterService(IServiceCollection services)
{
//篇幅有限,此处进展示这里
services.AddVampirewalCoreEventBusFactory();
}
}
2.1.2 注册进其他IServiceCollection
services.AddVampirewalCoreEventBusFactory();
2.2、使用
2.2.1、代码准备
public class TestEventBus : IVampirewalCoreEventFactory
{
public TestEventBus()
{
//此处构造函数中并没有其他参数,实际因为当前事件工厂已经被注册到了IServiceCollection中,所以其他被注册为单例的服务,可以在这里通过构造函数传进来
}
public string FactoryName => "测试用";
public void EventFactoryComplate(EventFactoryContext context)
{
}
[EventBusExcute(ExcuteSort = 3)]
public void TestEvent111111(EventContext context)
{
context.PassData = "已修改2";
}
[TestEventBusAop]
[EventBusExcute(ExcuteSort = 2)]
public void TestEvent2222222(EventContext context)
{
//throw new Exception("test");
context.PassData = "已修改1";
}
[EventBusExcute(ExcuteSort =1)]
public void haha123(EventContext context123)
{
context123.PassData = "已修改";
}
}
//该类为AOP使用
public class TestEventBusAopAttribute : EventBusAOPAttribute
{
public override void ExcuteAfter(EventContext context)
{
}
public override void ExcuteBefore(EventContext context)
{
}
}
2.2.2、代码说明
-
[EventBusExcute(ExcuteSort =1)]该特性的标记有2个作用
- 1、接收到消息之后,有这个特性的方法才会执行
- 2、使用PublishParallel推送的消息,并不会管ExcuteSort,只有Publish推送,会根据ExcuteSort进行排序,顺序执行 [TestEventBusAop]AOP特性
- 此处的AOP特性并不限一个,从上文的代码中可以看到,是根据标记在方法上的AOP数量进行顺序执行,可在方法上叠加多种AOP特性,仅需多次继承EventBusAOPAttribute并重写方法即可
2.2.3、调用
public class MainViewModel
{
public MainViewModel(IVampirewalCoreEventBusFactory factory)
{
factory.Publish("TestEventBus", "Hello World");
factory.Publish<TestEventBus>("Hello World");
factory.PublishParallel<TestEventBus>("Hello World");
factory.PublishParallel("TestEventBus", "Hello World");
}
}
总结
以上就是我这边构思的事件总线全部内容,请大家指正
总体来看,和能搜索出来的主流EventBus有点不一样,就是我这里并没有将订阅者标记到某一个方法上,而是通过一个事件工厂的方式,通篇执行完这个工厂内的所有有标记的方法。这个也是业务需要导致的,抛砖引玉吧。
大家有更好的想法可以一起交流哈