使用上下文信息进行分辨率谓词

时间:2015-06-05 11:40:11

标签: c# simple-injector

我有一个服务类型ITestGuard,我希望用FooTestGuardNullTestGuard来实现,具体取决于实例注入的表达式树。具体来说,除了解析请求的“祖先”类型为FooTestGuard之外,我想为所有情况提供TestController

我想我可以使用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代理?

1 个答案:

答案 0 :(得分:3)

您尝试实现的目标无法使用ExpressionBuilding事件完成。此事件允许您查看完整的对象图。当您的完整对象图仅由瞬态注册组成时,它似乎有效,但在使用任何其他生活方式时它会立即中断。不可能“往下看”。如果您正在处理表达式树,则为对象图。

RegisterWithContext方法受构建的Expression树的结构限制,但即使容器包含支持以向您提供有关注册父母的信息,这将是永远不会像你期望的那样锻炼。

最简单的证明是FooTestGuard的直接父母被注册为单身人士。由于Simple Injector保证注册Singleton生活方式,在容器实例中最多只有一个实例。但是不可能同时为该单个实例提供两个不同的ITestGuard依赖项。要解决这个问题,Simple Injector应该:

  1. 放松对单身人士的保证并创建ITestGuard父亲的两个实例,因此违反了只创建一个实例的承诺。
  2. 坚持只创建一个实例,这意味着根据首先解析的图表,该图表将包含FooTestGuardNullTestGuard
  3. 我希望这个简单的例子表明这两个选项都是非常糟糕的解决方案。这只是一个简单的例子。在使用其他生活方式或更复杂的对象图时,最终会陷入此陷阱并在您的应用程序中引入错误。

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

    因此,您可以更好地使用允许您在运行时切换实现的自定义代理类,而不是真正使您的配置复杂化。

    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());