Отражение ASP.NET Core 2.1 не работает при развертывании в AWS lambda - PullRequest
0 голосов
/ 13 мая 2019

У меня есть библиотека с приведенным ниже кодом для внедрения зависимостей.Это загружает все классы реализации, заканчивающиеся на Handler, и регистрирует их.

public static class HandlerRegistrationExtension
    {
        private static IDictionary<Type, Type> _decoratorsAttributes;

        public static void AddHandlers(this IServiceCollection services, IDictionary<Type, Type> decoratorsAttributes)
        {
            _decoratorsAttributes = decoratorsAttributes ?? new Dictionary<Type, Type>();

            List<Type> allAssembliesTypes = Assembly
                                    .GetEntryAssembly()
                                    .GetReferencedAssemblies()
                                    .Select(Assembly.Load)
                                    .SelectMany(a => a.GetTypes())
                                    .ToList();

            List<Type> handlerTypes = allAssembliesTypes
                                        .Where(x => x.GetInterfaces().Any(y => IsHandlerInterface(y)))
                                        .Where(x => x.Name.EndsWith("Handler", StringComparison.Ordinal))
                                        .ToList();

            foreach (Type type in handlerTypes)
            {
                AddHandler(services, type);
            }
        }

        private static void AddHandler(IServiceCollection services, Type type)
        {
            object[] attributes = type.GetCustomAttributes(false);

            List<Type> pipeline = attributes
                                    .Select(x => ToDecorator(x))
                                    .Concat(new[] { type })
                                    .Reverse()
                                    .ToList();

            Type interfaceType = type.GetInterfaces().Single(y => IsHandlerInterface(y));
            Func<IServiceProvider, object> factory = BuildPipeline(pipeline, interfaceType);

            services.AddTransient(interfaceType, factory);
        }

        private static Func<IServiceProvider, object> BuildPipeline(List<Type> pipeline, Type interfaceType)
        {
            List<ConstructorInfo> ctors = pipeline
                .Select(x =>
                {
                    Type type = x.IsGenericType ? x.MakeGenericType(interfaceType.GenericTypeArguments) : x;
                    return type.GetConstructors().Single();
                })
                .ToList();

            Func<IServiceProvider, object> func = provider =>
            {
                object current = null;

                foreach (ConstructorInfo ctor in ctors)
                {
                    List<ParameterInfo> parameterInfos = ctor.GetParameters().ToList();

                    object[] parameters = GetParameters(parameterInfos, current, provider);

                    current = ctor.Invoke(parameters);
                }

                return current;
            };

            return func;
        }

        private static object[] GetParameters(List<ParameterInfo> parameterInfos, object current, IServiceProvider provider)
        {
            var result = new object[parameterInfos.Count];

            for (int i = 0; i < parameterInfos.Count; i++)
            {
                result[i] = GetParameter(parameterInfos[i], current, provider);
            }

            return result;
        }

        private static object GetParameter(ParameterInfo parameterInfo, object current, IServiceProvider provider)
        {
            Type parameterType = parameterInfo.ParameterType;

            if (IsHandlerInterface(parameterType))
                return current;

            object service = provider.GetService(parameterType);
            if (service != null)
                return service;

            throw new ArgumentException($"Type {parameterType} not found");
        }

        private static Type ToDecorator(object attribute)
        {
            Type type = attribute.GetType();

            if (_decoratorsAttributes.ContainsKey(type))
            {
                return _decoratorsAttributes[type];
            }

            throw new ArgumentException(attribute.ToString());
        }

        private static bool IsHandlerInterface(Type type)
        {
            if (!type.IsGenericType)
                return false;

            Type typeDefinition = type.GetGenericTypeDefinition();

            return typeDefinition == typeof(ICommandHandler<,>) || typeDefinition == typeof(IQueryHandler<,>);
        }
    }

Когда я развертываю приложение в лямбда-функции AWS, кажется, что код, запрашивающий реализацию обработчика, не может быть найден.

private readonly IServiceProvider _provider;

        public MessagesDispatcher(IServiceProvider provider)
        {
            _provider = provider;
        }

        public async Task<T> DispatchAsync<T>(ICommand<T> command, CancellationToken cancellationToken)
        {
            Type type = typeof(ICommandHandler<,>);
            Type[] typeArgs = { command.GetType(), typeof(T) };
            Type handlerType = type.MakeGenericType(typeArgs);

            dynamic handler = _provider.GetService(handlerType);
            T result = await handler.HandleAsync((dynamic)command, cancellationToken);

            return result;
        }

Интересно, что может изменить развертывание приложения в лямбда-выражении, связанное со сборками, загруженными с отражением, поскольку код работает нормально при использовании LocalEntryPoint.cs.

1 Ответ

1 голос
/ 18 мая 2019

После небольшого процесса поиска и попытки / неудачи я обнаружил, что приведенный ниже метод получения сборки с определением типа для двоичного кода работает в среде AWS Lambda. Ниже приведен измененный метод AddHandlers.

public static void AddHandlers(
            this IServiceCollection services, 
            IDictionary<Type, Type> decoratorsAttributes,
            params Assembly[] assemblies) // Added a parameter to pass multiple assemblies
        {
            _decoratorsAttributes = decoratorsAttributes ?? new Dictionary<Type, Type>();

            List<Type> allAssembliesTypes = assemblies // Here we get the types from the assembly
                                                .SelectMany(a => a.GetTypes())
                                                .ToList();

            List<Type> handlerTypes = allAssembliesTypes
                                        .Where(x => x.GetInterfaces().Any(y => IsHandlerInterface(y)))
                                        .Where(x => x.Name.EndsWith("Handler", StringComparison.Ordinal))
                                        .ToList();

            foreach (Type type in handlerTypes)
            {
                AddHandler(services, type);
            }
        }

В Startup.cs я звоню AddHandlers, как показано ниже.

services.AddHandlers(new Dictionary<Type, Type>
            {
                { typeof(CircuitBreakerCommandDecoratorAttribute), typeof(CircuitBreakerCommandDecorator<,>) },
                { typeof(CircuitBreakerQueryDecoratorAttribute), typeof(CircuitBreakerQueryDecorator<,>) }
            },
            typeof(RegisterUserCommandHandler).GetTypeInfo().Assembly); .. This way the assembly containing the types I am scanning is loaded correctly
...