简体   繁体   中英

ASP.NET Core find a service in IServiceProvider that matches a UnderlyingSystemType

I have a method which accepts a list of domain events which implement the interface IDomainEvent . What I'm trying to do is get the underlying type of the IDomainEvent and then use IServiceProvider to get a service matching the underlying system type.

For example, this works;

TicketCreatedEvent event = new TicketCreatedEvent();
var service = _serviceProvider.GetService(typeof(IEventHandler<TicketCreatedEvent>));
// Where _serviceProvider is ASP.NET Cores standard DI provider.

But this doesn't as the event object is created as an interface rather than it's concrete type.

IDomainEvent event = new TicketCreatedEvent();
var service = _serviceProvider.GetService(typeof(IEventHandler<TicketCreatedEvent>));

I know I can retrieve the underlying type by using event.GetType().UnderlyingSystemType but I cannot figure out how to then use this within the _serviceProvider.GetService() , I thought I could have done the following, but it isn't valid;

IDomainEvent event = new TicketCreatedEvent();
Type type = event.GetType().UnderlyingSystemType;
var service = _serviceProvider.GetService(typeof(IEventHandler<type>));

Any advice on how to do this would be appreciated.

Sorry, it seems I missed some important information out of my question.

In my example above, the event is created just before the _serviceProvider is used, by in reality the event is passed to a method on an EventRaiser object, like this;

public static Raise<T>(T args) {
    var service = (IEventHandler<T>) _locator.GetService(typeof(IEventHandler<T>));
}

TicketCreatedEvent event = new TicketCreatedEvent();
Raise(event); // Works

IDomainEvent event = new TicketCreatedEvent();
Raise(event); // Doesn't work as it cannot locate the service

Where T is created using the interface, the service cannot be located, it only works if T is directly of the concrete implementation.

To help solving the problem, here is my complete DomainEventRaiser class. There are two important methods, the first is Raise', which will raise an event right away without being deferred. The second is Raise', which will raise an event right away without being deferred. The second is RaisedDeferred which accepts a list of events and handles them, RaisedDeferred` will be called when events need to be deferred, such as after a domain object has been persists.

public class DomainEventsRaiser
    {
        [ThreadStatic] //so that each thread has its own callbacks
        private static List<Delegate> _actions;

        // ASP.NET Core DI Service Provider
        private static IServiceProvider _locator;

        /// <summary>
        /// Lets the event raiser know what DI provider to use to find event handlers
        /// </summary>
        /// <param name="provider"></param>
        public static void RegisterProvider(IServiceProvider provider)
        {
            _locator = provider;
        }

        public static void Register2<T>(Action<T> callback) where T : IDomainEvent
        {
            if (_actions == null)
            {
                _actions = new List<Delegate>();
            }

            _actions.Add(callback);
        }

        public static void ClearCallbacks()
        {
            _actions = null;
        }

        /// <summary>
        /// Accepts a list of events, usually deferred events
        /// </summary>
        /// <param name="events">list of events to handle</param>
        public static void RaiseDeferred(IList<IDomainEvent> events)
        {
            if (events != null)
            {
                foreach (IDomainEvent ev in events)
                {
                    Type eventType = ev.GetType().UnderlyingSystemType;
                    Type genericHandlerType = typeof(IEventHandler<>);
                    Type constructedHandlerType = genericHandlerType.MakeGenericType(eventType);
                    var service =  _locator.GetService(constructedHandlerType);
                    constructedHandlerType.GetMethod("Handle").Invoke(service, null);

                    if (service != null)
                    {
                        service.Handle(args); // Cannot resolve symbol 'Handle'
                    }
                }
            }
        }

        /// <summary>
        /// Handles an event right away
        /// </summary>
        /// <param name="args">event arguments</param>
        /// <typeparam name="T">the event to handle</typeparam>
        public static void Raise<T>(T args) where T : IDomainEvent
        {     
            if (_locator != null)
            {   
                var service = (IEventHandler<T>) _locator.GetService(typeof(IEventHandler<T>));
                if (service != null)
                {
                    service.Handle(args);
                }
            }

            if (_actions != null)
            {
                foreach (var action in _actions)
                {
                    if (action is Action<T>)
                    {
                        ((Action<T>) action)(args);
                    }
                }
            }
        }
    }

You can probably do something like this:

IDomainEvent @event = new TicketCreatedEvent();
Type eventType = @event.GetType().UnderlyingSystemType;
Type genericHandlerType = typeof(IEventHandler<>);
Type constructedHandlerType = genericHandlerType.MakeGenericType(eventType);
var service = _serviceProvider.GetService(constructedHandlerType);

I assume you're registering your handlers in Startup.cs like:

services.AddTransient<IEventHandler<TicketCreatedEvent>, TicketCreatedEventHandler>();

For more info, see How to: Examine and Instantiate Generic Types with Reflection .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM