Microsoft DI - 是否在工厂实现中引用了对象?

Microsoft DI - Are objects referenced within a factory implementation disposed?

引用而非创建的对象是否在容器处理的工厂实现中?请参阅下面的代码:

services.AddTransient(c => OwinContext.ServiceObject);

考虑到容器未创建(即 new ServiceObject),实现 IDisposableServiceObject 是否会被容器处置?

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
    }
}