Microsoft DI - 是否在工厂实现中引用了对象?
Microsoft DI - Are objects referenced within a factory implementation disposed?
引用而非创建的对象是否在容器处理的工厂实现中?请参阅下面的代码:
services.AddTransient(c => OwinContext.ServiceObject);
考虑到容器未创建(即 new ServiceObject
),实现 IDisposable
的 ServiceObject
是否会被容器处置?
ServiceObject
目前注册为 Scoped,但我们在极少数情况下会得到 ObjectDisposedException
。我猜它有时会在我们的服务能够使用它之前就在 OWIN 中被处理掉,这就是为什么我希望让它成为 Transient 但我担心容器会更频繁地处理它。
一次性临时注册由容器跟踪并在其作用域结束时处理。
Microsoft 文档对此并不总是很清楚,因为 this 文档似乎暗示“框架不会自动处理”“不是由服务容器创建的”服务。尽管文档并没有错,因为它主要讨论通过 AddSingleton<T>(T instance)
扩展方法注册实例 — 它具有误导性,因为它不适用于:
AddSingleton<T>(Func<IServiceProvider, T>)
,
AddScoped<T>(Func<IServiceProvider, T>)
,以及
AddTransient<T>(Func<IServiceProvider, T>)
.
可以使用以下程序轻松验证此声明:
using Microsoft.Extensions.DependencyInjection;
var disposable = new FakeDisposable();
var services = new ServiceCollection();
services.AddTransient(c => disposable);
var provider = services.BuildServiceProvider(validateScopes: true);
using (var scope = provider.CreateScope())
{
scope.ServiceProvider.GetRequiredService<FakeDisposable>();
}
public class FakeDisposable : IDisposable
{
public void Dispose() => Console.WriteLine("Disposed");
}
输出:
Disposed
结论:是的,一次性对象的临时注册是容器处理的。
将此注册设置为瞬时注册或范围注册几乎没有什么区别。在这两种情况下,对象都将在作用域结束时被释放。
不过,在瞬时注册的情况下,您会开始看到一次性物品被多次处理,以防它被多次注入。例如:
using (var scope = provider.CreateScope())
{
scope.ServiceProvider.GetRequiredService<FakeDisposable>();
scope.ServiceProvider.GetRequiredService<FakeDisposable>();
scope.ServiceProvider.GetRequiredService<FakeDisposable>();
}
输出:
Disposed
Disposed
Disposed
然而,从可靠性来看,最好坚持使用 Scoped 注册,而不是 Transient。这是因为 MS.DI 将阻止 Scoped 注册被注入单例消费者(如果服务提供者是通过调用 BuildServiceProvider(validateScopes: true)
创建的)。万一你的 ServiceContext
被注入到一个单例中,它会导致它变成一个 Captive Dependency 并在它被处理很久之后继续被那个单例引用(并且可能被使用)。
您获得这些 ObjectDisposedException
的最可能原因是因为 Owin 在处理您的(网络请求)范围后尝试使用 ServiceContext
。
ServiceContext
对象很可能由 OWIN 控制和处置,这并不是由容器处置的理想对象。但这就是问题所在:MS.DI 将 总是 尝试处理瞬态和范围注册,防止这种情况发生的唯一方法是 not 注册你的 ServiceContext
.
因此,解决方案是将其包装在某种“提供者”对象中。例如:
// New abstraction
public interface IServiceObjectProvider
{
object ServiceObject { get; }
}
// Implementation part of your Composition Root (see: https://mng.bz/K1qZ)
public class AmbientOwinServiceObjectProvider : IServiceObjectProvider
{
public object ServiceObject => OwinContext.ServiceObject;
}
// Registration:
services.AddScoped<IServiceObjectProvider, AmbientOwinServiceObjectProvider>();
// Usage:
public class MyController : Controller
{
private readonly IServiceObjectProvider provider;
public MyController(IServiceObjectProvider provider)
{
// Only store the dependency here: don't use it,
// see: https://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/
this.provider = provider;
}
public string Index()
{
var so = this.provider.ServiceObject;
// Do something with the Service object
}
}
引用而非创建的对象是否在容器处理的工厂实现中?请参阅下面的代码:
services.AddTransient(c => OwinContext.ServiceObject);
考虑到容器未创建(即 new ServiceObject
),实现 IDisposable
的 ServiceObject
是否会被容器处置?
ServiceObject
目前注册为 Scoped,但我们在极少数情况下会得到 ObjectDisposedException
。我猜它有时会在我们的服务能够使用它之前就在 OWIN 中被处理掉,这就是为什么我希望让它成为 Transient 但我担心容器会更频繁地处理它。
一次性临时注册由容器跟踪并在其作用域结束时处理。
Microsoft 文档对此并不总是很清楚,因为 this 文档似乎暗示“框架不会自动处理”“不是由服务容器创建的”服务。尽管文档并没有错,因为它主要讨论通过 AddSingleton<T>(T instance)
扩展方法注册实例 — 它具有误导性,因为它不适用于:
AddSingleton<T>(Func<IServiceProvider, T>)
,AddScoped<T>(Func<IServiceProvider, T>)
,以及AddTransient<T>(Func<IServiceProvider, T>)
.
可以使用以下程序轻松验证此声明:
using Microsoft.Extensions.DependencyInjection;
var disposable = new FakeDisposable();
var services = new ServiceCollection();
services.AddTransient(c => disposable);
var provider = services.BuildServiceProvider(validateScopes: true);
using (var scope = provider.CreateScope())
{
scope.ServiceProvider.GetRequiredService<FakeDisposable>();
}
public class FakeDisposable : IDisposable
{
public void Dispose() => Console.WriteLine("Disposed");
}
输出:
Disposed
结论:是的,一次性对象的临时注册是容器处理的。
将此注册设置为瞬时注册或范围注册几乎没有什么区别。在这两种情况下,对象都将在作用域结束时被释放。
不过,在瞬时注册的情况下,您会开始看到一次性物品被多次处理,以防它被多次注入。例如:
using (var scope = provider.CreateScope())
{
scope.ServiceProvider.GetRequiredService<FakeDisposable>();
scope.ServiceProvider.GetRequiredService<FakeDisposable>();
scope.ServiceProvider.GetRequiredService<FakeDisposable>();
}
输出:
Disposed
Disposed
Disposed
然而,从可靠性来看,最好坚持使用 Scoped 注册,而不是 Transient。这是因为 MS.DI 将阻止 Scoped 注册被注入单例消费者(如果服务提供者是通过调用 BuildServiceProvider(validateScopes: true)
创建的)。万一你的 ServiceContext
被注入到一个单例中,它会导致它变成一个 Captive Dependency 并在它被处理很久之后继续被那个单例引用(并且可能被使用)。
您获得这些 ObjectDisposedException
的最可能原因是因为 Owin 在处理您的(网络请求)范围后尝试使用 ServiceContext
。
ServiceContext
对象很可能由 OWIN 控制和处置,这并不是由容器处置的理想对象。但这就是问题所在:MS.DI 将 总是 尝试处理瞬态和范围注册,防止这种情况发生的唯一方法是 not 注册你的 ServiceContext
.
因此,解决方案是将其包装在某种“提供者”对象中。例如:
// New abstraction
public interface IServiceObjectProvider
{
object ServiceObject { get; }
}
// Implementation part of your Composition Root (see: https://mng.bz/K1qZ)
public class AmbientOwinServiceObjectProvider : IServiceObjectProvider
{
public object ServiceObject => OwinContext.ServiceObject;
}
// Registration:
services.AddScoped<IServiceObjectProvider, AmbientOwinServiceObjectProvider>();
// Usage:
public class MyController : Controller
{
private readonly IServiceObjectProvider provider;
public MyController(IServiceObjectProvider provider)
{
// Only store the dependency here: don't use it,
// see: https://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/
this.provider = provider;
}
public string Index()
{
var so = this.provider.ServiceObject;
// Do something with the Service object
}
}