问题
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