使用简单注入器在C#中实现域事件处理程序模式

时间:2015-06-03 16:10:22

标签: c# generics inversion-of-control simple-injector

我正在尝试使用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方法中评论了我的问题。有没有人知道如何解决这个问题?

此致 加里

3 个答案:

答案 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);
}