ASP.NET Core 2.1 reflection doesn't work when deploying in AWS lambda

放肆的年华 提交于 2020-01-06 07:21:12

问题


I have a library with the below code for dependency injection. This loads all implementation classes ending with Handler and register them.

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<,>);
        }
    }

When I deploy the application in AWS Lambda function it seems like the code that is requesting an implementation of a handler could not be found.

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;
        }

I wonder what could change deploying the application in a lambda related to the assemblies loaded with reflection since the code works fine when using LocalEntryPoint.cs.


回答1:


After a bit of searching and try/fail process I found that the below method getting the Assembly with determining a type coming for the binary works in AWS Lambda environment. Below is the changed method 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);
            }
        }

In Startup.cs I call AddHandlers like below.

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


来源:https://stackoverflow.com/questions/56119647/asp-net-core-2-1-reflection-doesnt-work-when-deploying-in-aws-lambda

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!