下面是我教科书中的伪代码,我对在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-我将ConcreteA
和ConcreteB
都注册为AddScoped
,所以不应存在任何捕获的依赖项问题,因为它们属于同一范围,所以为什么ConcreteB
仍然会徘徊。
Q2-我什至没有创建ConcreteA实例,因为我访问的方法是静态方法,因此否需要创建ConcreteA实例。因此,ConcreteB
闲逛的可能性更大。
答案 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
将在同一范围内。