将上下文信息用于解析谓词

Using contextual information for resolution predicate

我有一个服务类型 ITestGuard,我想用 FooTestGuardNullTestGuard 来实现它,具体取决于注入实例的表达式树。具体来说,除了解析请求的 'ancestors' 之一的类型为 TestController.

之外,我想为所有情况提供 FooTestGuard

我想我可以用 ExpressionBuilding 事件来做到这一点,使用 this sample 作为指导,添加一个新的 Parent 属性 到 DependencyContext并通过递归下降填充它:

[DebuggerDisplay("DependencyContext (ServiceType: {ServiceType}, ImplementationType: {ImplementationType})")]
public class DependencyContext
{
    public static readonly DependencyContext Root = new DependencyContext();

    public DependencyContext(
        Type serviceType,
        Type implementationType,
        ParameterInfo parameter,
        DependencyContext parent = null)
    {
        ServiceType = serviceType;
        ImplementationType = implementationType;
        Parameter = parameter;
        Parent = parent;
    }

    private DependencyContext() { }

    public Type ServiceType { get; private set; }
    public Type ImplementationType { get; private set; }
    public ParameterInfo Parameter { get; private set; }
    public DependencyContext Parent { get; private set; }
}

public static class ContextDependentExtensions
{
    public static IEnumerable<DependencyContext> AncestorsAndSelf(this DependencyContext context)
    {
        while (true)
        {
            yield return context;
            if (context.Parent == null)
                yield break;
            context = context.Parent;
        }
    }

    public static void RegisterWithContext<TService>(this Container container,
        Func<DependencyContext, TService> contextBasedFactory) where TService : class
    {
        if (contextBasedFactory == null)
            throw new ArgumentNullException("contextBasedFactory");

        Func<TService> rootFactory = () => contextBasedFactory(DependencyContext.Root);
        container.Register(rootFactory, Lifestyle.Transient);

        // Allow the Func<DependencyContext, TService> to be injected into parent types.
        container.ExpressionBuilding += (sender, e) =>
        {
            if (e.RegisteredServiceType != typeof(TService))
            {
                var rewriter = new DependencyContextRewriter(
                    contextBasedFactory,
                    rootFactory,
                    e.RegisteredServiceType,
                    e.Expression);

                e.Expression = rewriter.Visit(e.Expression);
            }
        };
    }

    private sealed class DependencyContextRewriter : ExpressionVisitor
    {
        private readonly object _contextBasedFactory;
        private readonly object _rootFactory;
        private readonly Type _serviceType;
        private readonly Expression _expression;
        private readonly DependencyContext _parentContext;
        private readonly ParameterInfo _parameter;

        public DependencyContextRewriter(object contextBasedFactory,
            object rootFactory,
            Type serviceType,
            Expression expression,
            DependencyContext parentContext = null,
            ParameterInfo parameter = null)
        {
            _serviceType = serviceType;
            _contextBasedFactory = contextBasedFactory;
            _rootFactory = rootFactory;
            _expression = expression;
            _parentContext = parentContext;
            _parameter = parameter;
        }

        private Type ImplementationType
        {
            get
            {
                var expression = _expression as NewExpression;

                if (expression == null)
                    return _serviceType;

                return expression.Constructor.DeclaringType;
            }
        }

        protected override Expression VisitNew(NewExpression node)
        {
            var context = new DependencyContext(_serviceType, ImplementationType, _parameter, _parentContext);
            var parameters = node.Constructor.GetParameters();

            var rewritten = node.Arguments
                .Select((x, i) => new DependencyContextRewriter(_contextBasedFactory, _rootFactory, x.Type, x, context, parameters[i]).Visit(x));

            return node.Update(rewritten);
        }

        protected override Expression VisitInvocation(InvocationExpression node)
        {
            if (IsRootedContextBasedFactory(node))
                return Expression.Invoke(
                    Expression.Constant(_contextBasedFactory),
                    Expression.Constant(
                        new DependencyContext(
                            _serviceType,
                            ImplementationType,
                            _parameter,
                            new DependencyContext(_serviceType, ImplementationType, _parameter, _parentContext))));

            return base.VisitInvocation(node);
        }

        private bool IsRootedContextBasedFactory(InvocationExpression node)
        {
            var expression = node.Expression as ConstantExpression;

            if (expression == null)
                return false;

            return ReferenceEquals(expression.Value, _rootFactory);
        }
    }
}

但是,我看到的是 context 层次结构在传递给委托时并未完全填充。我在请求 TestController 时调试了访问者,然后按照它一直到 VisitInvocation 步骤以获得 ITestGuard。但是,IsRootedContextBasedFactory 检查返回 false,它跳过了委托替换。我认为这是因为它已经 替换为之前对 ExpressionBuilt 的调用,这意味着注册的表达式不再是 rootFactory 因此检查失败.

如何更改此访问者,以便它正确地将上下文信息(包括依赖关系层次结构)传递给 contextBasedFactory 委托?

使用 ExpressionBuilding 事件无法完成您想要实现的目标。此事件允许您查看完整的对象图。当您的完整对象图仅包含瞬时注册时,它似乎可以工作,但是当使用任何其他生活方式时它会立即中断。如果您正在处理表达式树,则 'look down' 对象图变得不可能。

RegisterWithContext 方法受构建的 Expression 树结构的限制,但即使容器包含支持为您提供有关注册父项的信息,这也永远行不通如您所料。

最简单的演示是当您的 FooTestGuard 的直接父级注册为单例时。由于 Simple Injector 保证使用 Singleton 生活方式的注册在容器实例中最多有一个实例。但是不可能同时给那个单一实例两个不同的 ITestGuard 依赖。为了解决这个问题,Simple Injector 应该:

  1. 放松对单例的保证并创建两个 ITestGuard 的父实例,因此违背了只创建一个实例的承诺。
  2. 坚持只创建一个实例的保证,这意味着根据首先解析哪个图,该图将包含 FooTestGuardNullTestGuard

我希望这个简单的例子表明这两个选项都是非常糟糕的解决方案。这只是一个简单的例子。当使用其他生活方式或更复杂的对象图时,最终很容易陷入这个陷阱并在您的应用程序中引入错误。

请注意,这不是 Simple Injector 的限制,而是一个数学真理。不要被误导,还有另一个 DI 库(阅读:Ninject)实际上允许您向上遍历对象图。您将遇到与我在此处描述的相同的问题。

因此,与其让您的配置真正复杂化,还不如使用允许您在运行时切换实现的自定义代理 class:

public class TestGuardSelector : ITestGuard
{
    private readonly Func<bool> selector;
    private readonly ITestGuard trueGuard;
    private readonly ITestGuard falseGuard;

    public TestGuardSelector(Func<bool> selector, ITestGuard trueGuard,
        ITestGuard falseGuard) {
        this.selector = selector;
        this.trueGuard = trueGuard;
        this.falseGuard = falseGuard;
    }

    public object TestGuardMethod(object value) {
        // Forward the call
        return this.CurrentGuard.TestGuardMethod(value);
    }

    private ITestGuard CurrentGuard {
        get { return this.selector() ? this.trueGuard : this.falseGuard; }
    }
}

这个代理可以注册如下:

container.RegisterSingle<ITestGuard>(new TestGuardSelector(
    () => HttpContext.Current.Request.Url.Contains(@"\Test\"),
    new FooTestGuard(),
    new NullTestGuard());