【问题标题】:Get per-request dependency from an Autofac func factory从 Autofac func 工厂获取每个请求的依赖关系
【发布时间】:2017-06-18 03:55:43
【问题描述】:

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

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

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

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

【问题讨论】:

  • 你不能重新设计你的单例服务来扭转依赖关系吗?如果它真的需要一个 DbContext,为什么它首先是单例的?
  • @Tseng 不,不幸的是我不能。它是一个 FluentValidation 验证器,包含几十个 lambda 表达式,只需编译和缓存一次。
  • 但不妨碍它被重构。不知道您的单例服务是什么样子的,但是将流式验证拆分为自己的单例服务然后将其与 DbContext 一起注入到您使用的范围服务中可能是有意义的。但是如果没有更多信息,很难给你具体的提示
  • @Tseng 单例服务是一个验证器类,由 FluentValidation 库注册和使用。在这方面我无能为力。这个特定的验证器需要在做出决定之前检查数据库中的某些内容,这就是注入 DbContext 的原因。但我想要和其他地方一样的实例,而不是一个新实例。
  • 好吧,该类的具体示例及其用法会很有用,但仍有可用的“蛮力”方法:注入 IHttpContextAccessor 并从那里解析 DbContext,您将始终具有作用域(除非在请求之外调用验证器,即通过后台运行任务 - 您应该已经在 ASP.NET(核心)应用程序中避免这种情况)。但它非常粗暴,并将您的验证器与基础设施(ASP.NET Core/HttpAbstraction 库)联系起来。或者创建一个 Scoped<T> 类并注入它(就像你对 Owned 所做的那样

标签: c# asp.net-core autofac


【解决方案1】:

对于 cmets,最不“架构”痛苦的方法可能是创建自己的 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));
    }
}

注册为

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

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

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

通过这种方式,您可以注入任何范围内的服务,而无需进一步注册。

补充说明

Autofac 被认为是“conformer”(请参阅​​docs)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);
}

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

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

这对于让 ASP.NET Core 在 ASP.NET Core 中的任何地方都使用容器非常重要,即使在通过 HttpContext.RequestedServices 解析每个请求依赖关系的中间件内部也是如此。

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

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

【讨论】:

  • 首先,这真的很聪明并且有效,所以谢谢,至少我现在没有被卡住!然而,这不是变相的Service Locator吗?它基本上可以解决任何问题。
  • 我希望有一种方法可以进行某种自定义注册。另一方面,也许我可以使用您的解决方案,但限制 T 所以它是 where T : DbContext 所以它只解析上下文。
  • 是和不是。 IScoped&lt;T&gt; 类是您的基础架构的一部分(与 Autofac 和 ASP.NET Core 相同),因此它位于您的域之外,逻辑所在的域和域类和服务对此一无所知。唯一知道它的层是应用程序层,并且仅在复合根中,具体Scope&lt;T&gt; 注册的地方。您的所有服务仍然可以轻松模拟,因为容器不会在您的服务中仅在 Scoped&lt;T&gt; 内调用。我保持它的通用性,以便它可以轻松用于各种范围服务
  • 如果您在每个域类中注入IServiceProvider/IHttpContextAccessor/IContainer 并调用.Resolve 那么它将是一个真正的服务定位器并且很难测试/模拟它并且当架构发生变化时,您可以轻松替换它而不会影响您的域
  • 我已经更新了一些背景信息以及应该如何集成第 3 方 DI。我称它为IScoped&lt;T&gt; 是为了明确该类的意图,它将返回一个作用域实例,并且更明显的是使用它时应该特别小心。您也可以将其称为IRequestScoped&lt;T&gt;,但这已经暗示它始终连接到请求。对于控制台应用程序,您可以以不同的方式实现它,因为没有请求,但您的域服务不必关心它
猜你喜欢
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 2012-03-21
  • 1970-01-01
  • 1970-01-01
  • 2017-08-02
  • 1970-01-01
相关资源
最近更新 更多