我正在尝试使用Simple Injector在C#中实现域事件模式。
我已将我的代码简化为一个文件,该文件可作为控制台应用程序运行,并已排除Simple Injector代码,以便为此问题保持清晰。
我遇到的问题是每个事件都可能有多个事件处理程序,并且可能会引发多个事件但是我想限制我的Dispatcher只处理实现IEvent
接口的事件,所以我把这个限制在我的调度方法上。
这引起了如何从Simple Injector获取实例的问题,因为每次调用Dispatch
方法TEvent
类型为IEvent
时(正如我所料),但我需要获取传入的事件类型,以便我可以从Simple Injector获取相关的处理程序。
希望我的代码能够更好地解释这一点:
interface IEvent
{
}
interface IEventHandler<T> where T : IEvent
{
void Handle(T @event);
}
class StandardEvent : IEvent
{
}
class AnotherEvent : IEvent
{
}
class StandardEventHandler : IEventHandler<StandardEvent>
{
public void Handle(StandardEvent @event)
{
Console.WriteLine("StandardEvent handled");
}
}
class AnotherEventHandler : IEventHandler<AnotherEvent>
{
public void Handle(AnotherEvent @event)
{
Console.WriteLine("AnotherEvent handled");
}
}
这是我的调度员:
static class Dispatcher
{
// I need to get the type of @event here so I can get the registered instance from the
// IoC container (SimpleInjector), however TEvent is of type IEvent (as expected).
// What I need to do here is Get the registered instance from Simple Injector for each
// Event Type i.e. Container.GetAllInstances<IEventHandler<StandardEvent>>()
// and Container.GetAllInstances<IEventHandler<AnotherEvent>>()
public static void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent
{
}
}
class PlainOldObject
{
public ICollection<IEvent> Events = new List<IEvent>
{
new StandardEvent(),
new AnotherEvent()
};
}
class StandAlone
{
static void Main(string[] args)
{
var poco = new PlainOldObject();
foreach (var @event in poco.Events)
{
Dispatcher.Dispatch(@event);
}
}
}
我在Dispatch方法中评论了我的问题。有没有人知道如何解决这个问题?
此致 加里
答案 0 :(得分:9)
您需要的解决方案有点依赖于Dispatcher
的消费者如何调用事件。如果使用者在编译时始终知道事件的确切类型,则可以使用上面显示的通用Dispatch<TEvent>(TEvent)
方法。在这种情况下,Dispatcher
的实现将非常简单。
另一方面,消费者可能并不总是知道确切的类型,而只是使用IEvent
接口,Dispatch<TEvent>(TEvent)
中的泛型类型参数变得无用,你最好定义一下一个Dispatch(IEvent)
方法。这使得实现更加复杂,因为您需要使用反射来解决这个问题。
另请注意,引入IEventDispatcher
抽象会很好。不要在代码中调用静态类。甚至Udi Dahan(很久以前最初described such static class)现在认为这是一种反模式。相反,将IEventDispatcher
抽象注入需要事件调度的类中。
如果所有使用者都使用编译时已知的事件类型,您的实现将如下所示:
public interface IEventDispatcher
{
void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent;
}
private sealed class Dispatcher : IEventDispatcher
{
private readonly Container container;
public Dispatcher(Container container) {
this.container = container;
}
public void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent {
if (@event == null) throw new ArgumentNullException("event");
var handlers = this.container.GetAllInstances<IEventHandler<TEvent>>();
foreach (var handler in handlers) {
handler.Handle(@event);
}
}
}
另一方面,如果事件类型未知,您可以使用以下代码:
public interface IEventDispatcher
{
void Dispatch(IEvent @event);
}
private sealed class Dispatcher : IEventDispatcher
{
private readonly Container container;
public Dispatcher(Container container) {
this.container = container;
}
public void Dispatch(IEvent @event) {
if (@event == null) throw new ArgumentNullException("event");
Type handlerType = typeof(IEventHandler<>).MakeGenericType(@event.GetType());
var handlers = this.container.GetAllInstances(handlerType);
foreach (dynamic handler in handlers) {
handler.Handle((dynamic)@event);
}
}
}
请注意,与使用.NET反射API相比,使用dynamic关键字有一些不明显的优势。例如,当使用dynamic调用处理程序的Handle
方法时,从句柄抛出的任何异常都会直接冒泡。另一方面,当使用MethodInfo.Invoke
时,异常将包含在新的异常中。这使得捕获和调试更加困难。
您的事件处理程序可以按如下方式注册:
container.RegisterCollection(typeof(IEventHandler<>), listOfAssembliesToSearch);
答案 1 :(得分:0)
要使用SimpleInjector并动态注入域事件,您可以执行以下操作:
在SI注册中
_container.Register(typeof(IDomainEventHandler<>), new[] { typeof(IDomainEventHandler<>).Assembly});
然后创建一个事件
public class PolicyAddressChangedEvent : IDomainEvent
{
public Address NewAddress { get; }
public Address OriginalAddress { get; }
public PolicyAddressChangedEvent(Address oldBillingAddress, Address newbillingAddress)
{
OriginalAddress = oldBillingAddress;
NewAddress = newbillingAddress;
}
}
然后为事件
创建一个处理程序public class PolicyAddressChangeHandler : IDomainEventHandler<PolicyAddressChangedEvent>
{
private readonly ILoggingService _loggingService;
public PolicyAddressChangeHandler(ILoggingService loggingService)
{
_loggingService = loggingService;
}
public void Handle(PolicyAddressChangedEvent domainEvent)
{
_loggingService.Info("New policy address recorded", new Dictionary<string, object> { { "new address", domainEvent.NewAddress } }, "FrameworkSample");
//this could be event hub, queues, or signalR messages, updating a data warehouse, sending emails, or even updating other domain contexts
}
}
现在,在使用简单进样器创建IDomainEventDistpatcher时注入正确的注射器,可以使用工厂进样器。这是获取所有类型并能够动态查找它们的关键。通过这样做,我们将一个Func注入DomainEventDispatcher。
_container.RegisterSingleton<IDomainEventDispatcher>(() =>
{
return new DomainEventDispatcher(type => _container.GetInstance(type));
});
现在在DomainEventDispatcher中我们有
public class DomainEventDispatcher : IDomainEventDispatcher
{
private readonly Func<Type, object> _handlerLookup;
public DomainEventDispatcher(Func<Type, object> handlerLookup)
{
_handlerLookup = handlerLookup;
}
public void Dispatch(IDomainEvent domainEvent)
{
Type handlerType = typeof(IDomainEventHandler<>).MakeGenericType(domainEvent.GetType());
var handler = GetHandler(handlerType);
if (handler != null)
{
handler.Handle((dynamic)domainEvent);
}
}
private dynamic GetHandler(Type filterType)
{
try
{
object handler = _handlerLookup.Invoke(filterType);
return handler;
}
catch (Exception)
{
return null;
}
}
}
现在接受IDomainEvent并创建正确的类型,并根据提供的Func查找它。
这是更好的,因为现在我们不强制对类的依赖来了解我们正在使用的DI实现。非常类似于史蒂文的上面的anwser(有一些小的推文),只是想也会提供一个完整的例子。
答案 2 :(得分:0)
这是使用缓存的委托的更快版本。第一次调用后没有动态,也没有反射。使用默认Microsoft DI中的IServiceProvider
,但可以轻松更改。表达式树也可以使用,但是会占用更多内存:
public class EventDispatcherService : IEventDispatcher
{
private static readonly ConcurrentDictionary<Type, IEnumerable<Func<object, Task>>> HandlersCache
= new ConcurrentDictionary<Type, IEnumerable<Func<object, Task>>>();
private static readonly Type HandlerType = typeof(IEventHandler<>);
private static readonly MethodInfo MakeDelegateMethod = typeof(EventDispatcherService)
.GetMethod(nameof(MakeDelegate), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly Type OpenGenericFuncType = typeof(Func<,>);
private static readonly Type TaskType = typeof(Task);
private readonly IServiceProvider serviceProvider;
public EventDispatcherService(IServiceProvider serviceProvider)
=> this.serviceProvider = serviceProvider;
public async Task Dispatch(IDomainEvent domainEvent)
{
var eventHandlers = HandlersCache.GetOrAdd(domainEvent.GetType(), eventType =>
{
var eventHandlerType = HandlerType.MakeGenericType(eventType);
var makeDelegate = MakeDelegateMethod.MakeGenericMethod(eventType);
var funcType = OpenGenericFuncType.MakeGenericType(eventType, TaskType);
return this.serviceProvider
.GetServices(eventHandlerType)
.Select(handler => handler
.GetType()
.GetMethod("Handle")
.CreateDelegate(funcType, handler))
.Select(handlerDelegateConcrete => (Func<object, Task>)makeDelegate
.Invoke(null, new object[] { handlerDelegateConcrete }))
.ToList();
});
foreach (var eventHandler in eventHandlers)
{
await eventHandler(domainEvent);
}
}
private static Func<object, Task> MakeDelegate<T>(Func<T, Task> action)
=> value => action((T)value);
}
这是事件处理程序界面:
public interface IEventHandler<in TEvent>
where TEvent : IDomainEvent
{
Task Handle(TEvent domainEvent);
}