我有一个服务类型ITestGuard
,我希望用FooTestGuard
或NullTestGuard
来实现,具体取决于实例注入的表达式树。具体来说,除了解析请求的“祖先”类型为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
代理?
答案 0 :(得分:3)
您尝试实现的目标无法使用ExpressionBuilding
事件完成。此事件允许您查看完整的对象图。当您的完整对象图仅由瞬态注册组成时,它似乎有效,但在使用任何其他生活方式时它会立即中断。不可能“往下看”。如果您正在处理表达式树,则为对象图。
RegisterWithContext
方法受构建的Expression
树的结构限制,但即使容器包含支持以向您提供有关注册父母的信息,这将是永远不会像你期望的那样锻炼。
最简单的证明是FooTestGuard
的直接父母被注册为单身人士。由于Simple Injector保证注册Singleton
生活方式,在容器实例中最多只有一个实例。但是不可能同时为该单个实例提供两个不同的ITestGuard
依赖项。要解决这个问题,Simple Injector应该:
ITestGuard
父亲的两个实例,因此违反了只创建一个实例的承诺。FooTestGuard
或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());