在Configure方法中使用服务时超出范围?

时间:2019-08-27 00:50:38

标签: c# dependency-injection .net-core asp.net-core-mvc

下面是我教科书中的伪代码,我对在configure方法中使用服务感到困惑

public class ConcreteA
{
   public static Run(IServiceProvider serviceProvider)
   {
      ConcreteB _concrete = serviceProvider.GetRequiredService<ConcreteB>();
      ... //use ConcreteB  instance
   }

}
__________________________________________________________

// startup.cs
public void ConfigureServices(IServiceCollection services) 
{
   services.AddScoped<ConcreteA>;
   services.AddScoped<ConcreteB>;  
}

public void Configure(IApplicationBuilder app) {
   app.UseStatusCodePages();
   app.UseDeveloperExceptionPage();
   app.UseMvcWithDefaultRoute();
   ConcreteA.Run(app.ApplicationServices);
}

有人告诉我,因为我在ConcreteA方法中使用了Configure,所以我在范围之外运行它。我创建的任何依赖项(在这种情况下为ConcreteB实例)都会徘徊。

我很困惑,以下是我的问题:

Q1-我将ConcreteAConcreteB都注册为AddScoped,所以不应存在任何捕获的依赖项问题,因为它们属于同一范围,所以为什么ConcreteB仍然会徘徊。

Q2-我什至没有创建ConcreteA实例,因为我访问的方法是静态方法,因此需要创建ConcreteA实例。因此,ConcreteB闲逛的可能性更大。

2 个答案:

答案 0 :(得分:3)

要明确地回答您的困惑:您必须将服务提供者视为对象的缓存。创建未注册为临时服务的服务时,它将在本地存储该实例,以便稍后可以再次提供 same 实例。

例如,当您执行以下操作(假设IFoo未注册为瞬态)时,它将解析相同的对象实例:

serviceProvider.GetService<IFoo>();
serviceProvider.GetService<IFoo>();

为此,服务提供商必须记住它在第一个调用中返回的IFoo,因此它可以在第二个调用(以及任何其他调用)中返回相同的实例。

那么范围是什么?作用域基本上告诉服务提供商从单独缓存中解析服务。当您在范围内并且现在解析范围服务IScopedBar时,服务提供商为您创建的实例将被缓存在该范围缓存中:

serviceProvider.GetService<IScopedBar>();
serviceProvider.GetService<IScopedBar>();

当您在合并范围内并解析单例服务时,该服务仍将在主缓存中查找。但是范围服务将在范围缓存中查找。

如果不关闭合并范围,那么一切都不会真正改变。当关闭作用域时(这是在处置它时完成的)(例如,using块结束时),则作用域缓存中的服务将被处置并清除缓存。但是,主缓存仍然保留。

如果我们要在简化的伪服务提供程序类中实现它,它可能看起来像这样:

public class SimplifiedServiceProvider
{
    private Dictionary<Type, object> mainCache = new Dictionary<Type, object>();
    private Dictionary<Type, object> scopeCache = new Dictionary<Type, object>();

    public object GetService(Type type)
    {
         var serviceLifetime = GetLifetimeForService(type);

         if (serviceLifetime == ServiceLifetime.Transient)
         {
             // transients are created directly
             return CreateNewInstance(type);
         }
         else if (serviceLifetime == ServiceLifetime.Singleton)
         {
             // try to get from the cache
             if (!mainCache.TryGetValue(type, out var service))
             {
                 // create the service first
                 service = CreateNewInstance(type);
                 mainCache.Add(type, service);
             }
             return service;
         }
         else if (serviceLifetime == ServiceLifetime.Scoped)
         {
             // try to get from the scope cache
             if (!scopeCache.TryGetValue(type, out var service))
             {
                 // create the service first
                 service = CreateNewInstance(type);
                 scopeCache.Add(type, service);
             }
             return service;
         }
    }

    public void DisposeScope()
    {
        // dispose all created (disposable) instances
        foreach (var instance in scopeCache.Values)
            (instance as IDisposable)?.Dispose();

        // reset cache
        scopeCache.Clear();
    }

    private ServiceLifetime GetLifetimeForService(Type type) { … }
    private object CreateNewInstance(Type type) { … }
}

(服务提供者和服务范围的真正实现显然要比这复杂得多,但这仍应使您更好地了解范围依赖与单例之间的区别。)

牢记这个想法和伪实现,想像一下当您在范围之外解析范围服务时会发生什么,因此DisposeScope永远不会被调用:创建的范围服务将永久保留在范围缓存中;就像单例永久保留在主缓存中一样。

因此,通过在服务范围之外解析作用域服务 ,您可以有效地将实例的生存期提高为单例服务。这不会影响在范围内实际 创建的实例,但是在范围外创建的那些实例将在服务提供者的生存期内生存,通常是应用程序的生存期。< / p>

这就是为什么当您要使用“自然范围”以外的范围服务(即,像ASP.NET Core处理请求时自动为您创建的范围)时通常希望创建一个临时范围的原因。这样,您就可以限制该作用域的生存期,因此也可以限制您解析的实例的生存期。

答案 1 :(得分:2)

这似乎是XY problem

我相信您正在尝试实现以下目标,但首先您需要通过构造函数注入重构yourTable.findAll({ /* ... */ }) .then(async (data) => { if (data && data.length) return data[0]._id const result = await myTable.create({ /* ... */ }) return result._id }) 使其显式依赖于ConcreteA(尽管由于代码异味而依赖于混凝土)当前形式的问题的范围(原谅双关语)

ConcreteB

然后在启动时将它们注册为作用域,就像之前在ConfigureServices中一样。但是,在public class ConcreteA { private ConcreteB B; public ConcreteA(ConcreteB B) { this.B = B; } public void Run() { ... //use ConcreteB instance } } 中,您将按范围访问它们。

例如

startup.cs

Configure

上面使用的public void ConfigureServices(IServiceCollection services) { services.AddScoped<ConcreteA>(); services.AddScoped<ConcreteB>(); //... } public void Configure(IApplicationBuilder app) { app.UseStatusCodePages(); app.UseDeveloperExceptionPage(); app.UseMvcWithDefaultRoute(); // Create a new IServiceScope that can be used to resolve scoped services. using(var scope = app.ApplicationServices.CreateScope()) { // resolve the services within this scope ConcreteA A = scope.ServiceProvider.GetRequiredService<ConcreteA>(); //ConcreteA instance and injected ConcreteB are used in the same scope //do something A.Run(); } //both will be properly disposed of here when they both got out of scope. } 和扩展名ConcreteA将在同一范围内。