简体   繁体   English

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

[英]Get per-request dependency from an Autofac func factory

I'm using ASP.NET Core and Autofac. 我正在使用ASP.NET Core和Autofac。 Almost everything is registered as per lifetime scope ("per request"). 几乎所有内容都是按照生存期范围注册的(“每个请求”)。 So my database context DbContext is the same instance throughout a request. 因此,我的数据库上下文DbContext是整个请求中的同一实例。

However I have a singleton which also depends on DbContext . 但是我有一个单例,它也取决于DbContext To avoid a captive dependency, it is injected as Func<Owned<DbContext>> , which means a new DbContext instance each time. 为了避免强制依赖性,将其作为Func<Owned<DbContext>>注入,这每次都意味着一个新的DbContext实例。

The problem is I need the same instance, as everywhere else during the request, not a new one. 问题是我需要与请求期间其他所有地方相同的实例,而不是新实例。

I want to avoid a captive dependency bug, but I also want the same instance. 我想避免一个圈养的依赖错误,但是我也想要同一个实例。 Is that possible via tagging or a custom registration? 是否可以通过标记或自定义注册?

From the comments the least "architectural" painful approach may be by creating your own Scoped<T> class which will resolve the DbContext from current HttpContext 从注释中,最“痛苦的”建筑方法可能是通过创建自己的Scoped<T>类,该类将从当前HttpContext解析DbContext。

// 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));
    }
}

Register it as 注册为

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

Then inject this into your validator service. 然后将其注入您的验证器服务。

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

This way you can inject any scoped service w/o further registrations. 这样,您可以在不进行进一步注册的情况下注入任何作用域服务。

Additional notes 补充笔记

Autofac is considered a "conformer" (see docs ) DI and integrates well with ASP.NET Core and Microsoft.Extensions.DependencyInjection. Autofac被认为是“合格者” DI(请参阅docs ),并且与ASP.NET Core和Microsoft.Extensions.DependencyInjection集成良好。

From the documentation 从文档中

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

There a few subtle differences to the default usage of Startup class and Microsoft.Extensions.DependencyInjection container. Startup类和Microsoft.Extensions.DependencyInjection容器的默认用法之间有一些细微的差异。

  1. ConfigureServices isn't void anymore, it returns IServiceProvider . ConfigureServices不再void ,它返回IServiceProvider This will tell ASP.NET Core to use the returned provider instead of DefaultServiceProvider from Microsoft.Extensions.DependencyInjection . 这将告诉ASP.NET Core使用返回的提供程序,而不是Microsoft.Extensions.DependencyInjectionDefaultServiceProvider
  2. We return the Autofac container adapter: new AutofacServiceProvider(this.ApplicationContainer) which is the root container. 我们返回Autofac容器适配器: new AutofacServiceProvider(this.ApplicationContainer) ,它是根容器。

This is important to make ASP.NET Core use the container everywhere in ASP.NET Core, even inside middlewares which resolve per request dependencies via HttpContext.RequestedServices . 这对于使ASP.NET Core在ASP.NET Core中的所有位置使用容器都是很重要的,即使在中间件内部也可以通过HttpContext.RequestedServices解析每个请求的依赖关系。

For that reasons you can't use .InstancePerRequest() lifetime in Autofac, because Autofac isn't in control of creating scopes and only ASP.NET Core can do it. 因此,您不能在Autofac中使用.InstancePerRequest()生存期,因为Autofac不能控制范围的创建,只有ASP.NET Core可以做到。 So there is no easy way to make ASP.NET Core use Autofac's own Request lifetime. 因此,没有简单的方法可以使ASP.NET Core使用Autofac自己的请求生存期。

Instead ASP.NET Core will create a new scope (using IServiceScopeFactory.CreateScope() ) and use a scoped container of Autofac to resolve per-request dependencies. 相反,ASP.NET Core将创建一个新范围(使用IServiceScopeFactory.CreateScope() ),并使用Autofac范围内的容器来解析每个请求的依赖关系。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM