检查 autofac 中间件内的构造函数参数
Inspecting constructor parameters inside an autofac middleware
我有以下场景:
public interface IService
{
string Name { get; }
}
public class A : IService
{
public string Name => "A";
}
public class B : IService
{
public string Name => "B";
}
public class C
{
public C(IService first, IService second)
{
Console.WriteLine(first.Name);
Console.WriteLine(second.Name);
}
public C(IService single)
{
Console.WriteLine(single.Name);
}
}
我正在使用 Autofac 作为我的 DI 容器。我想要的是让 Autofac 解决 class C
的依赖关系,在相同服务类型的多个参数的情况下使用参数名称作为键, 但仅在那case(例子中的single
参数应该可以正常解析)
所以最终目标是要有这种行为:
var builder = new ContainerBuilder();
builder.RegisterType<A>().Keyed<IService>("first");
builder.RegisterType<B>().Keyed<IService>("second");
builder.RegisterType<C>().AsSelf();
// possibly modify the container behavior here
C test = builder.Build().Resolve<C>();
// output:
// A
// B
在我的用例中,对每个注册中的行为进行硬编码(比如使用 RegistrationExtensions.WithParameter
)不是一个选项。
使用 Autofac 的 middleware feature 我只设法覆盖 all 参数的解析行为(无论参数类型是否在构造函数中多次使用)。
这是我目前所拥有的:
public class ParameterNameAsKeyMiddleware : IResolveMiddleware
{
public PipelinePhase Phase => PipelinePhase.ParameterSelection;
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
{
Parameter parameterKeyedByName = new ResolvedParameter(
(p, i) => true,
(p, i) =>
{
if (i.TryResolveKeyed(p.Name, p.ParameterType, out object instance))
{
return instance;
}
return i.Resolve(p.ParameterType);
}
);
var parameters = context.Parameters.Union(new[] { parameterKeyedByName });
context.ChangeParameters(parameters);
// Continue the resolve.
next(context);
}
}
并且我在构建容器之前添加了以下代码:
// Add custom middleware to every registration.
builder.ComponentRegistryBuilder.Registered += (sender, args) =>
{
args.ComponentRegistration.PipelineBuilding += (sender2, pipeline) =>
{
pipeline.Use(new ParameterNameAsKeyMiddleware());
};
};
如有任何帮助,我们将不胜感激。
更新:
感谢 Alistair 的回答,这是一个可行的解决方案:
public class ParameterNameAsKeyMiddleware : IResolveMiddleware
{
private static readonly ConcurrentDictionary<ConstructorInfo, IEnumerable<IGrouping<Type, ParameterInfo>>>
duplicationCache = new ConcurrentDictionary<ConstructorInfo, IEnumerable<IGrouping<Type, ParameterInfo>>>();
public PipelinePhase Phase => PipelinePhase.ParameterSelection;
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
{
Parameter parameterKeyedByName = new ResolvedParameter(
DuplicatedServicePredicate,
(pi, ctx) => ctx.ResolveKeyed(pi.Name, pi.ParameterType));
var parameters = context.Parameters.Union(new[] { parameterKeyedByName });
context.ChangeParameters(parameters);
// Continue the resolve.
next(context);
#region Local functions, called only from the context of this method
bool DuplicatedServicePredicate(ParameterInfo p, IComponentContext i)
{
var constructor = p.Member as ConstructorInfo;
IEnumerable<IGrouping<Type, ParameterInfo>> duplications = GetDuplicateServices(constructor);
bool isDuplicate = duplications.Any(g => g.Key == p.ParameterType);
return isDuplicate;
// gets groupings of multiple parameters implementing the same service (type).
// if present, value is fetched from cache. otherwise value is reflected.
IEnumerable<IGrouping<Type, ParameterInfo>> GetDuplicateServices(ConstructorInfo constructorInfo)
{
if (!duplicationCache.TryGetValue(
constructorInfo,
out IEnumerable<IGrouping<Type, ParameterInfo>> duplicateServices))
{
duplicateServices = constructorInfo.GetParameters()
.GroupBy(pi => pi.ParameterType)
.Where(g => g.Count() > 1);
duplicationCache.AddOrUpdate(
constructorInfo, duplicateServices,
(x, y) => throw new InvalidOperationException("values should only be added"));
}
return duplicateServices;
}
}
#endregion
}
}
我相信有一种方法可以做到这一点,即从 ParameterInfo
.
跳到 MethodInfo
您需要在当前 return 为真的地方实施条件谓词。
在该谓词中,您应该在提供的 ParameterInfo
、p
上访问 Member
属性,这会让您进入构造函数方法(转换为 MethodInfo
).从那里,您可以访问参数列表并确定是否有多个相同类型的参数。 Return 如果所有参数都具有唯一类型,则为 false,这样可以进行正常的 Autofac 解析。
您可能想考虑将检查的结果缓存在构造函数 MemberInfo
上键入的 ConcurrentDictionary
中,这样您就不必每次都进行检查。
我有以下场景:
public interface IService
{
string Name { get; }
}
public class A : IService
{
public string Name => "A";
}
public class B : IService
{
public string Name => "B";
}
public class C
{
public C(IService first, IService second)
{
Console.WriteLine(first.Name);
Console.WriteLine(second.Name);
}
public C(IService single)
{
Console.WriteLine(single.Name);
}
}
我正在使用 Autofac 作为我的 DI 容器。我想要的是让 Autofac 解决 class C
的依赖关系,在相同服务类型的多个参数的情况下使用参数名称作为键, 但仅在那case(例子中的single
参数应该可以正常解析)
所以最终目标是要有这种行为:
var builder = new ContainerBuilder();
builder.RegisterType<A>().Keyed<IService>("first");
builder.RegisterType<B>().Keyed<IService>("second");
builder.RegisterType<C>().AsSelf();
// possibly modify the container behavior here
C test = builder.Build().Resolve<C>();
// output:
// A
// B
在我的用例中,对每个注册中的行为进行硬编码(比如使用 RegistrationExtensions.WithParameter
)不是一个选项。
使用 Autofac 的 middleware feature 我只设法覆盖 all 参数的解析行为(无论参数类型是否在构造函数中多次使用)。
这是我目前所拥有的:
public class ParameterNameAsKeyMiddleware : IResolveMiddleware
{
public PipelinePhase Phase => PipelinePhase.ParameterSelection;
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
{
Parameter parameterKeyedByName = new ResolvedParameter(
(p, i) => true,
(p, i) =>
{
if (i.TryResolveKeyed(p.Name, p.ParameterType, out object instance))
{
return instance;
}
return i.Resolve(p.ParameterType);
}
);
var parameters = context.Parameters.Union(new[] { parameterKeyedByName });
context.ChangeParameters(parameters);
// Continue the resolve.
next(context);
}
}
并且我在构建容器之前添加了以下代码:
// Add custom middleware to every registration.
builder.ComponentRegistryBuilder.Registered += (sender, args) =>
{
args.ComponentRegistration.PipelineBuilding += (sender2, pipeline) =>
{
pipeline.Use(new ParameterNameAsKeyMiddleware());
};
};
如有任何帮助,我们将不胜感激。
更新: 感谢 Alistair 的回答,这是一个可行的解决方案:
public class ParameterNameAsKeyMiddleware : IResolveMiddleware
{
private static readonly ConcurrentDictionary<ConstructorInfo, IEnumerable<IGrouping<Type, ParameterInfo>>>
duplicationCache = new ConcurrentDictionary<ConstructorInfo, IEnumerable<IGrouping<Type, ParameterInfo>>>();
public PipelinePhase Phase => PipelinePhase.ParameterSelection;
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
{
Parameter parameterKeyedByName = new ResolvedParameter(
DuplicatedServicePredicate,
(pi, ctx) => ctx.ResolveKeyed(pi.Name, pi.ParameterType));
var parameters = context.Parameters.Union(new[] { parameterKeyedByName });
context.ChangeParameters(parameters);
// Continue the resolve.
next(context);
#region Local functions, called only from the context of this method
bool DuplicatedServicePredicate(ParameterInfo p, IComponentContext i)
{
var constructor = p.Member as ConstructorInfo;
IEnumerable<IGrouping<Type, ParameterInfo>> duplications = GetDuplicateServices(constructor);
bool isDuplicate = duplications.Any(g => g.Key == p.ParameterType);
return isDuplicate;
// gets groupings of multiple parameters implementing the same service (type).
// if present, value is fetched from cache. otherwise value is reflected.
IEnumerable<IGrouping<Type, ParameterInfo>> GetDuplicateServices(ConstructorInfo constructorInfo)
{
if (!duplicationCache.TryGetValue(
constructorInfo,
out IEnumerable<IGrouping<Type, ParameterInfo>> duplicateServices))
{
duplicateServices = constructorInfo.GetParameters()
.GroupBy(pi => pi.ParameterType)
.Where(g => g.Count() > 1);
duplicationCache.AddOrUpdate(
constructorInfo, duplicateServices,
(x, y) => throw new InvalidOperationException("values should only be added"));
}
return duplicateServices;
}
}
#endregion
}
}
我相信有一种方法可以做到这一点,即从 ParameterInfo
.
MethodInfo
您需要在当前 return 为真的地方实施条件谓词。
在该谓词中,您应该在提供的 ParameterInfo
、p
上访问 Member
属性,这会让您进入构造函数方法(转换为 MethodInfo
).从那里,您可以访问参数列表并确定是否有多个相同类型的参数。 Return 如果所有参数都具有唯一类型,则为 false,这样可以进行正常的 Autofac 解析。
您可能想考虑将检查的结果缓存在构造函数 MemberInfo
上键入的 ConcurrentDictionary
中,这样您就不必每次都进行检查。