使用上下文信息进行解析谓词

亚历克斯·福克斯·吉尔

我有一个服务类型ITestGuard,我想使用FooTestGuard来实现NullTestGuard,具体取决于实例被注入其中的表达式树。具体来说,我想FooTestGuard为所有情况提供解决方案,但解决方案请求的“祖先”类型为时除外TestController

我以为这个ExpressionBuilding事件可以做到这一点,可以使用此示例作为准则,向中添加一个新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事件无法完成您想要达到的目标此事件使您可以查看完整的对象图。当您的完整对象图仅由瞬态注册组成时,它似乎可以工作,但是当使用其他生活方式时,它将立即中断。如果要处理表达式树,则无法“向下看”对象图。

RegisterWithContext方法受构建Expression的结构限制,但是即使容器包含支持为您提供有关注册父母的信息的支持,也永远不会如您所愿。

最简单的说明是将您的直接父母FooTestGuard注册为单身人士。因为Simple Injector可以保证在Singleton生活方式实例中注册一个容器实例中最多包含一个实例。但是不可能同时给单个实例两个不同的ITestGuard依赖关系。要解决此问题,Simple Injector应该:

  1. 松开对singleton的保证,并创建ITestGuard的父对象的两个实例,因此违反了仅创建一个实例的承诺。
  2. 坚持只创建一个实例的保证,这意味着根据首先解析哪个图,该图将包含aFooTestGuard或a NullTestGuard

我希望这个简单的示例表明这两个选项都是不好的解决方案。这只是一个简单的例子。当与其他生活方式或更复杂的对象图一起工作时,最终落入这个陷阱并在应用程序中引入错误真的很容易。

请注意,这不是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());

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

使用上下文信息进行解析谓词

来自分类Dev

在Coq模式匹配中使用上下文信息

来自分类Dev

在Coq模式匹配中使用上下文信息

来自分类Dev

正确使用上下文

来自分类Dev

在AppCompatActivity中使用上下文

来自分类Dev

正确使用上下文

来自分类Dev

在AppCompatActivity中使用上下文

来自分类Dev

在片段中使用上下文

来自分类Dev

使用上下文的问题

来自分类Dev

在特定视图中使用上下文操作栏时,无法解析方法startActionMode()

来自分类Dev

jersey中json解析的应用上下文配置

来自分类Dev

使用上下文的React组件很难进行单元测试

来自分类Dev

如何对使用上下文的类进行单元测试?

来自分类Dev

如何使用上下文对象在基于类的视图中进行查询?

来自分类Dev

使用上下文变量在百里香叶中进行预处理

来自分类Dev

使用Mockito测试使用上下文的函数

来自分类Dev

在React中使用上下文在兄弟姐妹之间传递上下文

来自分类Dev

在上下文之外使用上下文中的字符串

来自分类Dev

使用上下文菜单时保留JavaScript链接

来自分类Dev

即使使用上下文也无法引用findViewbyId()

来自分类Dev

在片段中使用上下文时出错

来自分类Dev

Django-使用上下文重定向

来自分类Dev

如何在函数中使用上下文?

来自分类Dev

在使用上下文之前预先加载它

来自分类Dev

使用Java禁用上下文菜单(右键单击)

来自分类Dev

在片段中使用上下文的最佳方法

来自分类Dev

如何测试使用上下文参数的Flask API

来自分类Dev

如何实际使用上下文?

来自分类Dev

在React中使用上下文API