是否可以将 ServiceProvider 传递给构造函数参数?

Is it possible to pass the ServiceProvider into a constructor parameter?

问题:

我们有一个 .NET 5 WPF 应用程序,它有一个 EntityFramework 核心实体 class 文件 DbEntities,它实现了 DbContext。我们在实例化它时使用构造函数注入。我们使用的选项之一是 AddInterceptors,以便将访问令牌附加到 SqlConnection。拦截器称为AzureAuthenticationInterceptor。在注册服务时,我们希望传入 ServiceProvider 以便在拦截器构造函数中可用,可用于获取实现 Access Token 内存缓存的服务。

原因是我们有一个包含 50 多个 class 的项目,它们都使用相同的 DbEntities 文件,在构造函数中采用 0 个参数。这已升级到 .NET 5,由于将其应用于所有表单所需的工作,因此避免了依赖注入。因此,DbEntitiesnew DbEntities();.

的形式实例化

但是,在这种情况下,我们要实现一个访问令牌缓存,需要将其注册为服务。否则,如果我们每次新建DbContext就直接实例化缓存,那么缓存就会被清空

访问令牌内存缓存是使用此方法实现的https://mderriey.com/2020/09/12/resolve-ef-core-interceptors-with-dependency-injection/

我们只想对内存中的令牌缓存使用依赖注入。我们认为是捷径的唯一方法是在拦截器的构造函数中传递 ServiceProvider,但它在 ConfigureServices 方法中似乎不可用。

问题:

是否可以传入ServiceProvider?如果没有,有没有其他方法我们可以在拦截器上实现依赖注入而无需更改 50 class 个文件?

Program.cs

Public static void Main()
{
...
    Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
        .ConfigureAppConfiguration((context, builder) =>
        {
            builder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
        })
        .ConfigureServices((context, services) =>
        {
            Configuration = context.Configuration;
            ConfigureServices(Configuration, services);
        })
        .Build();
...
}

private static void ConfigureServices(IConfiguration objConfiguration, IServiceCollection objServices)
{
    objServices.AddMemoryCache()
        .AddSingleton<IAzureSqlTokenProvider, AzureIdentityAzureSqlTokenProvider>()
        .Decorate<IAzureSqlTokenProvider, CacheAzureSqlTokenProvider>()
        .AddSingleton(new AzureAuthenticationInterceptor(IServiceProvider_NeededHere))
        ;
}

DbEntities.cs

public DbEntities() :
base(new DbContextOptionsBuilder<DbEntities>()
    .UseSqlServer(ConfigurationManager.ConnectionStrings["DbEntities"].ConnectionString)
    .AddInterceptors(new AzureAuthenticationInterceptor())
    .Options)
{ }

AzureAuthenticationInterceptor.cs

public AzureAuthenticationInterceptor(IServiceProvider objServiceProvider)
{
    this.IAzureSqlTokenProvider = (IAzureSqlTokenProvider)objServiceProvider.GetService(typeof(IAzureSqlTokenProvider));
}   

首先,避免注入 IServiceProvider,这是一种代码味道,会导致糟糕的设计。

重构AzureAuthenticationInterceptor.cs

public AzureAuthenticationInterceptor(IAzureSqlTokenProvider tokenProvider) {
    this.IAzureSqlTokenProvider = tokenProvider;
}   

这样可以根据需要注入显式依赖关系

//...

.AddSingleton<AzureAuthenticationInterceptor>()

//...

在配置 DbEntities 时解析拦截器

//...

services.AddDbContext<DbEntities>((provider, options) => {
    options.UseSqlServer(Configuration.GetConnectionString("<connection-string-name>"));
    options.AddInterceptors(provider.GetRequiredService<AzureAuthenticationInterceptor>());
});

//...

请注意,如果您使用默认构造函数手动初始化上下文,即:new DbEntities(); 那么这将绕过通过构造函数注入应用依赖注入的机会。