使用Simple Injector在C#中实现域事件处理程序模式

我正在尝试使用Simple Injector在C#中实现域事件模式。

我已经将我的代码简化为一个文件,该文件可以作为控制台应用程序运行,并且已经排除了Simple Injector代码,以便为此问题保持清晰。

IEvent的问题是每个事件可能有多个事件处理程序,并且可能会引发多个事件,但我想限制我的Dispatcher只处理实现IEvent接口的事件,所以我把这个限制放在我的Dispatch方法上。

这导致了如何从Simple Injector获取实例的问题,因为每次调用Dispatch方法时TEvent都是IEvent类型(正如我所期望的那样)但是我需要获取传入的事件类型以便我可以获得相关的Simple Injector的处理程序。

希望我的代码能够更好地解释这个:

 interface IEvent { } interface IEventHandler where T : IEvent { void Handle(T @event); } class StandardEvent : IEvent { } class AnotherEvent : IEvent { } class StandardEventHandler : IEventHandler { public void Handle(StandardEvent @event) { Console.WriteLine("StandardEvent handled"); } } class AnotherEventHandler : IEventHandler { 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 ie Container.GetAllInstances<IEventHandler>() // and Container.GetAllInstances<IEventHandler>() public static void Dispatch(TEvent @event) where TEvent : IEvent { } } class PlainOldObject { public ICollection Events = new List { 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方法中评论了我的问题。 有没有人知道如何解决这个问题?

问候,加里

您需要的解决方案有点依赖于Dispatcher的消费者如何调用事件。 如果使用者总是在编译时知道事件的确切类型,则可以使用上面显示的通用Dispatch(TEvent)方法。 在这种情况下, Dispatcher的实现将非常简单。

另一方面,消费者可能并不总是知道确切的类型,但只是使用IEvent接口, Dispatch(TEvent)的generics类型参数变得无用,你最好定义一个Dispatch(IEvent)方法。 这使得实现更加复杂,因为您需要使用reflection来解决这个问题。

另请注意,引入IEventDispatcher抽象会很好。 不要在代码中调用静态类。 甚至Udi Dahan(很久以前最初描述过这种静态类 )现在也认为这是一种反模式。 相反,将IEventDispatcher抽象注入需要事件调度的类中。

如果所有使用者都使用编译时已知的事件类型,您的实现将如下所示:

 public interface IEventDispatcher { void Dispatch(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 @event) where TEvent : IEvent { if (@event == null) throw new ArgumentNullException("event"); var handlers = this.container.GetAllInstances>(); 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); } } } 

请注意,与使用.NETreflectionAPI相比,使用dynamic关键字有一些不明显的优势。 例如,当使用dynamic调用处理程序的Handle方法时,从句柄抛出的任何exception都会直接冒泡。 另一方面,当使用MethodInfo.Invoke时,exception将包装在新的exception中。 这使得捕获和调试更加困难。

您的事件处理程序可以注册如下:

 container.RegisterCollection(typeof(IEventHandler<>), listOfAssembliesToSearch); 

要使用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 { private readonly ILoggingService _loggingService; public PolicyAddressChangeHandler(ILoggingService loggingService) { _loggingService = loggingService; } public void Handle(PolicyAddressChangedEvent domainEvent) { _loggingService.Info("New policy address recorded", new Dictionary { { "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(() => { return new DomainEventDispatcher(type => _container.GetInstance(type)); }); 

现在我们在DomainEventDispatcher中

 public class DomainEventDispatcher : IDomainEventDispatcher { private readonly Func _handlerLookup; public DomainEventDispatcher(Func 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实现。 非常类似于Steven的上面的anwser(有一些小的tweek),只是想也会提供一个完整的例子。