【问题标题】:Inspecting constructor parameters inside an autofac middleware检查 autofac 中间件中的构造函数参数
【发布时间】:2021-02-16 11:04:42
【问题描述】:

我有以下场景:

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 解决类 C 的依赖关系,在相同服务类型的多个参数的情况下使用参数名称作为键,但仅在这种情况下(示例中的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
    }
}

【问题讨论】:

    标签: .net dependency-injection autofac ioc-container


    【解决方案1】:

    我相信有一种方法可以做到这一点,即从ParameterInfo 跳到MethodInfo

    您需要在当前只返回 true 的地方实现条件谓词。

    在该谓词中,您应该访问提供的ParameterInfop 上的Member 属性,这将让您进入构造函数方法(强制转换为MethodInfo)。从那里,您可以访问参数列表并确定是否有多个相同类型的参数。如果所有参数都具有唯一类型,则返回 false,这样就可以进行正常的 Autofac 解析。

    您可能想要考虑将该检查的结果缓存在以构造函数的MemberInfo 为键的ConcurrentDictionary 中,因此您不必每次都进行该检查。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-01
      • 2015-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多