我对Autofac的生存期范围有疑问。可以在应用程序的不同位置创建多个范围以解析类型吗?
在官方文档中有这样的声明:
注意:通常来说,服务位置在很大程度上被认为是一种反模式也就是说,到处手动创建作用域并通过代码大量使用容器不一定是最好的方法。使用Autofac集成库,您通常不必执行我们在上面的示例应用程序中所做的工作。相反,事情是从应用程序的中央“顶层”位置解决的,而手动解决方案很少。当然,如何设计应用程序取决于您。
如果我有多个视图模型,而每个视图模型都需要解析某些类型怎么办?
示例:
public class ClassA
{
//some code here
}
public class ClassB
{
//some code here
}
public class ClassC
{
//some code here
}
public class ViewModelA
{
public ViewModelA()
{
}
public void Method()
{
//some code here
using (var scope = Container.BeginLifetimeScope())
{
var typeC = scope.Resolve<ClassC>();
//some code here
}
}
}
public class ViewModelB
{
public ViewModelB()
{
}
public void Method()
{
//some code here
using (var scope = Container.BeginLifetimeScope())
{
var typeA = scope.Resolve<ClassA>();
var typeB = scope.Resolve<ClassB>();
//some code here
}
}
}
假设所有类型都在容器中注册-在应用程序中分布这样的范围是一种好习惯吗?您如何理解?
最诚挚的问候。
TL; DR
我对Autofac的生存期范围有疑问。可以在应用程序的不同位置创建多个范围以解析类型吗?
是的,尽管被认为不是最佳方法,但还是被允许的,但绝对不是被禁止的方法。如果我有多个视图模型,而每个视图模型都需要解析某些类型怎么办?
只需按照@Raymond的建议,通过构造函数注入来解决它们。您不需要像解决问题一样简单的作用域。
现在,让我们再谈一点。
首先,如何使用构造函数注入?没有比这更简单的了。让我们稍微修改一下示例:
public class ClassA
{
//some code here
}
public class ClassB
{
//some code here
}
public class ClassC
{
//some code here
}
public class ViewModelA
{
private ClassC typeC;
// this is a constructor injection - yes, it's that simple.
public ViewModelA(ClassC _typeC)
{
typeC = _typeC;
}
public void Method()
{
typeC.doStuff();
}
}
public class ViewModelB
{
private ClassA typeA;
private ClassB typeB;
public ViewModelB(ClassA _typeA, ClassB _typeB)
{
typeA = _typeA;
typeB = _typeB;
}
public void Method()
{
typeA.doStuff();
typeB.doAnotherStuff();
}
}
就是这样。您只需在构造函数中指定参数,然后将这些参数保存在类的私有字段中的某个位置即可。只要确定它们确实存在就使用这些对象。 (尽管它们可以是可选的,但我现在将略过)。如果autofac无法解析某些依赖关系,它将抛出异常-因此,如果您的类已创建,则可以绝对确定所有依赖关系都已解析并通过构造函数参数提供给它。 (同样,为了简洁起见,我将跳过一些更复杂的场景)。
它给我们带来什么?好了,现在ViewModelA
和ViewModelB
类通常对任何容器或DI一无所知。他们所知道的是某人(读为“容器”)将以某种方式从外部提供几个指定类型的参数(通常是接口)。现在,类本身对谁将创建其依赖项,何时,何地以及如何方式一无所知,这不再是它的关注点。这就是依赖项注入的全部要点:消除了管理依赖项的麻烦。
[下一件事是-此图中的范围在哪里?好吧...无处。 :)这是理想的情况:服务完全不知道DI容器的存在。
但是,终生范围是什么?它是控制(惊奇!)从中解决的对象的生存期的一种手段。通常,您不需要它,除非您要处理一些极端情况。
让我们看一下极其简化的示例。假设您有一些工作需要按计划对外部资源执行某种请求。但是,执行请求的方法实际上是通用的:它事先不知道它执行的是哪种确切类型,因为它是由该方法的类型参数定义的,该参数仅在运行时才知道。
还可以说,您还有另一个要求:您不必将api服务保留在内存中,而只为执行请求而创建它们,然后在完成工作后立即将其丢弃。
它可能类似于以下内容:
abstract class SomeSpecialApi<TSomeTypeParam> { // just some common base type
public abstract string doGetStuff();
}
class SomeSpecialApi1 : SomeSpecialApi<SomeTypeParam1> where SomeTypeParam1 : TSomeTypeParam {
private HttpClient _client;
public SomeSpecialApi(HttpClient client) {
_client = client;
}
public override string doGetStuff() {
return _client.get(someUrlWeDontCareAbout).Result;
}
}
class SomeSpecialApi2 : SomeSpecialApi<SomeTypeParam2> where SomeTypeParam2 : TSomeTypeParam {
private SomeFtpClient _client;
public SomeSpecialApi(SomeFtpClient client) {
_client = client;
}
public override string doGetStuff() {
// it's just something very different from the previous class
return _client.read(someOtherPathWeDontCareAboutEither).Result;
}
}
class JobPerformer {
private ILifeTimeScope _scope;
public JobPerformer(ILifeTimeScope scope) {
_scope = scope;
}
void PerformJob<T>() {
while (true) {
using (var childScope = scope.BeginLifeTimeScope()) {
var api = childScope.Resolve<SomeSpecialApi<T>>();
var result = api.doGetStuff();
// do something with the result
} // here the childScope and everything resolved from it will be destroyed
// and that's the whole purpose of the lifetime scopes.
Thread.Sleep(1000);
}
}
}
您知道它是如何工作的吗?根据方法的类型,参数容器将解析不同的类以完成工作。因此,与其创建所有依赖项,然后手动销毁它们,您要做的只是创建自己的作用域,请求一个所需的服务实例,然后在完成工作后销毁作用域。范围销毁负责销毁您甚至(也不应该)知道的所有依赖项。与手动创建所有依赖项相比,这绝对是一个进步。
但是,这里的缺点是现在JobPerformer类必须知道诸如ILifeTimeScope之类的东西。因此,从某种意义上说,它类似于ServiceLocator模式:我们至少必须知道