使用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
方法。 在这种情况下, Dispatcher
的实现将非常简单。
另一方面,消费者可能并不总是知道确切的类型,但只是使用IEvent
接口, Dispatch
的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),只是想也会提供一个完整的例子。