将上下文信息用于解析谓词
Using contextual information for resolution predicate
我有一个服务类型 ITestGuard
,我想用 FooTestGuard
或 NullTestGuard
来实现它,具体取决于注入实例的表达式树。具体来说,除了解析请求的 '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 应该:
- 放松对单例的保证并创建两个
ITestGuard
的父实例,因此违背了只创建一个实例的承诺。
- 坚持只创建一个实例的保证,这意味着根据首先解析哪个图,该图将包含
FooTestGuard
或 NullTestGuard
。
我希望这个简单的例子表明这两个选项都是非常糟糕的解决方案。这只是一个简单的例子。当使用其他生活方式或更复杂的对象图时,最终很容易陷入这个陷阱并在您的应用程序中引入错误。
请注意,这不是 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());
我有一个服务类型 ITestGuard
,我想用 FooTestGuard
或 NullTestGuard
来实现它,具体取决于注入实例的表达式树。具体来说,除了解析请求的 '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 应该:
- 放松对单例的保证并创建两个
ITestGuard
的父实例,因此违背了只创建一个实例的承诺。 - 坚持只创建一个实例的保证,这意味着根据首先解析哪个图,该图将包含
FooTestGuard
或NullTestGuard
。
我希望这个简单的例子表明这两个选项都是非常糟糕的解决方案。这只是一个简单的例子。当使用其他生活方式或更复杂的对象图时,最终很容易陷入这个陷阱并在您的应用程序中引入错误。
请注意,这不是 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());