我有一个在某个时候创建生命周期范围的应用程序,
public class Main
{
public void Main()
{
using (ILifetimeScope scope = AutofacContainer.Container.BeginLifetimeScope())
{
scope.Resolve<SomeClass>();
}
}
}
在SomeClass
内,我有一个逻辑,该逻辑然后调用许多不同的类,依此类推。
然后,在调用堆栈中大约有10种方法,我需要使用主作用域来做到这一点:
public class ActivatorFactory : IActivatorFactory
{
public T Create<T>(Type instance)
{
using (ILifetimeScope scope = AutofacContainer.Container.BeginLifetimeScope())
{
return (T)scope.Resolve(instance);
}
}
}
问题在于,现在我创建了一个新作用域,该作用域仅用于解析运行时类型。我希望能够使用主作用域来解决此类型。在不通过10种不同的方法/功能将主范围传递给该工厂类的情况下,该怎么办?
我想到的唯一“ hacky”解决方案是在ActivatorFactory
类上仅具有一个静态属性,并在Main
类中设置范围,如下所示:
public class Main
{
public void Main()
{
using (ILifetimeScope scope = AutofacContainer.Container.BeginLifetimeScope())
{
ActivatorFactory.Scope = scope;
scope.Resolve<SomeClass>();
}
}
}
是否有更干净的解决方案可以在应用程序的另一部分中使用主范围?
答案 0 :(得分:1)
在每个生命周期范围内,我都需要一个CancellationTokenSource
实例,其中子级与父级链接。如果root
范围的CancellationTokenSource
被取消,则所有子生存期范围的CancellationToken
被取消。为此,我创建了:
private sealed class ParentLifetimeScopeAccessor
{
private readonly ILifetimeScope _lifetimeScope;
public ParentLifetimeScopeAccessor(ILifetimeScope lifetimeScope)
{
_lifetimeScope = lifetimeScope;
_lifetimeScope.ChildLifetimeScopeBeginning += OnChildLifetimeScopeBeginning;
}
public ILifetimeScope ParentLifetimeScope { get; private set; }
private void OnChildLifetimeScopeBeginning(object sender, LifetimeScopeBeginningEventArgs e) =>
e.LifetimeScope.Resolve<ParentLifetimeScopeAccessor>().ParentLifetimeScope = _lifetimeScope;
}
注册后,您现在可以访问父母的范围:
builder.RegisterType<ParentLifetimeScopeAccessor>().InstancePerLifetimeScope();
使用父生命周期作用域访问器,可以创建链接的CancellationTokenSource
实例:
private static CancellationTokenSource CancellationTokenSourceFactory(IComponentContext context)
{
var scopeAccessor = context.Resolve<ParentLifetimeScopeAccessor>();
var parentScope = scopeAccessor.ParentLifetimeScope;
return null == parentScope
? new CancellationTokenSource()
: CancellationTokenSource.CreateLinkedTokenSource(parentScope.Resolve<CancellationTokenSource>().Token);
}
CancellationToken
解析器:
private static CancellationToken CancellationTokenResolver(IComponentContext context) =>
context.Resolve<CancellationTokenSource>().Token;
两个注册:
builder.Register(CancellationTokenSourceFactory).AsSelf().InstancePerLifetimeScope();
builder.Register(CancellationTokenResolver).AsSelf().InstancePerDependency();
答案 1 :(得分:0)
如果您的应用未使用ActivatorFactory
(如果您使用的是控制反转,则不应该使用),则将其删除,然后考虑一下您要使用的是什么尝试测试。
让我们说这是最后一件事-您有一个非常复杂且麻烦的对象图,您想确保可以创建它,因为人们一直在破坏它。我可以看到。
将注册分离到一个Autofac模块中。使用Autofac模块进行测试。
public class MyRegistrations : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<Thing>();
// and all your other registrations.
}
}
然后在单元测试中
var builder = new ContainerBuilder();
builder.RegisterModule<MyRegistrations>();
var container = builder.Build();
var thing = container.Resolve<Thing>();
// Assert on the resolved thing.
您可以使用相同的模块将您的注册封装在应用程序中,然后您实际上将在测试复杂的注册,但是没有未使用的工厂。
警告:测试一些复杂的注册与测试所有注册之间的滑坡。就像我说的那样,您真的不想测试您拥有的每个注册。我跌倒了。这是一场维护噩梦。向模块/应用添加注册,添加测试。嗯,我们重构了,现在注册都不同了。啊。对行为的测试要比对特征的测试少(不是“我要它做什么”,而是“现在要做什么”)。疼痛。痛苦。
如果您正在在应用中使用ActivatorFactory
,例如,将其作为服务位置,而不是使用已经存在的CommonServiceLocator之类的更标准的东西为您和Autofac already directly integrates ...为您执行此操作,然后仅使用真实容器对ActivatorFactory
进行测试,但使用一些任意测试注册,而不是真实应用程序中的整个测试集。 ActivatorFactory
的功能与其中注册的内容没有任何关系。
是的,如果您使用的是ActivatorFactory
,并且需要保留它,则必须在应用启动时将其交给ILifetimeScope
。这就是服务定位器的工作方式。在查看如何与ASP.NET,WCF等应用程序集成时,您会在Autofac docs中看到所有这些信息。