从 Autofac 函数工厂获取每个请求的依赖关系

gro*_*kky 3 c# autofac asp.net-core

我正在使用 ASP.NET Core 和 Autofac。几乎所有内容都是按照生命周期范围(“每个请求”)注册的。所以我的数据库上下文DbContext在整个请求中是同一个实例。

不过我有一个单例,它也依赖于DbContext. 为了避免强制依赖,它被注入为,这意味着每次都有Func<Owned<DbContext>>一个新实例。DbContext

问题是我需要与请求期间其他地方相同的实例,而不是新的实例。

我想避免强制依赖错误,但我也想要相同的实例。可以通过标记或自定义注册实现这一点吗?

Tse*_*eng 5

从评论来看,最不“架构”痛苦的方法可能是创建您自己的Scoped<T>类,它将从当前解析 DbContextHttpContext

// Use an interface, so we don't have infrastructure dependencies in our domain
public interface IScoped<T> where T : class
{
    T Instance { get; }
}

// Register as singleton too.
public sealed class Scoped<T> : IScoped<T> where T : class
{
    private readonly IHttpContextAccessor contextAccessor;
    private HttpContext HttpContext { get; } => contextAccessor.HttpContext;

    public T Instance { get; } => HttpContext.RequestServices.GetService<T>();

    public Scoped(IHttpContextAccessor contextAccessor)
    {
        this.contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor));
    }
}
Run Code Online (Sandbox Code Playgroud)

将其注册为

// Microsoft.Extensions.DependencyInjection
services.AddSingleton(typeof(IScoped<>), typeof(Scoped<>);
// Autofac
containerBuilder.RegisterType(typeof(Scoped<>))
            .As(typeof(IScoped<>));
Run Code Online (Sandbox Code Playgroud)

然后将其注入您的验证器服务中。

public class CustomerValidator: AbstractValidator<Customer>
{
    private readonly IScoped<AppDbContext> scopedContext;
    protected AppDbContext DbContext { get } => scopedContext.Instance;

    public CustomValidator(IScoped<AppDbContext> scopedContext)
    {
        this.scopedContext = scopedContext ?? throw new ArgumentNullException(nameof(scopedContext));

        // Access DbContext via this.DbContext
    }
}
Run Code Online (Sandbox Code Playgroud)

这样您就可以注入任何范围内的服务而无需进一步注册。

补充笔记

Autofac 被认为是“符合者”(请参阅​​文档)DI,并且与 ASP.NET Core 和 Microsoft.Extensions.DependencyInjection 集成良好。

从文档中

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    // Add services to the collection.
    services.AddMvc();

    // Create the container builder.
    var builder = new ContainerBuilder();

    // Register dependencies, populate the services from
    // the collection, and build the container. If you want
    // to dispose of the container at the end of the app,
    // be sure to keep a reference to it as a property or field.
    builder.RegisterType<MyType>().As<IMyType>();
    builder.Populate(services);
    this.ApplicationContainer = builder.Build();

    // Create the IServiceProvider based on the container.
    return new AutofacServiceProvider(this.ApplicationContainer);
}
Run Code Online (Sandbox Code Playgroud)

Startup类和容器的默认用法有一些细微的差别Microsoft.Extensions.DependencyInjection

  1. ConfigureServices不再是了void,它又回来了IServiceProvider。这将告诉 ASP.NET Core 使用返回的提供程序而不是DefaultServiceProvider来自Microsoft.Extensions.DependencyInjection.
  2. 我们返回 Autofac 容器适配器:new AutofacServiceProvider(this.ApplicationContainer)它是根容器。

这对于让 ASP.NET Core 在 ASP.NET Core 中的任何地方使用容器非常重要,甚至在通过HttpContext.RequestedServices.

因此,您不能.InstancePerRequest()在 Autofac 中使用生命周期,因为 Autofac 无法控制创建范围,并且只有 ASP.NET Core 可以做到这一点。因此没有简单的方法可以让 ASP.NET Core 使用 Autofac 自己的 Request 生命周期。

相反,ASP.NET Core 将创建一个新作用域(使用IServiceScopeFactory.CreateScope())并使用 Autofac 的作用域容器来解析每个请求的依赖关系。

  • 我已经更新了更多背景信息以及如何集成第 3 方 DI。我将其称为“IScoped&lt;T&gt;”是为了明确类的意图,它将返回一个作用域实例,并且更明显地表明使用它时应该特别小心。您也可以将其称为“IRequestScoped&lt;T&gt;”,但这已经意味着它始终连接到请求。对于控制台应用程序,您可以以不同的方式实现它,因为没有请求,但您的域服务不必关心它 (2认同)