我有一个服务类型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应该:
ITestGuard
的父对象的两个实例,因此违反了仅创建一个实例的承诺。FooTestGuard
或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] 删除。
我来说两句