【问题标题】:disable the dependency injection scope validation feature in the Program class?禁用 Program 类中的依赖注入范围验证功能?
【发布时间】:2019-08-26 21:45:26
【问题描述】:

我的教科书展示了一个构建身份服务的示例,下面是代码:

//startup.cs    
public void Configure(IApplicationBuilder app) {
   app.UseStatusCodePages();
   app.UseDeveloperExceptionPage();
   app.UseStaticFiles();
   app.UseAuthentication();
   app.UseMvcWithDefaultRoute();
   //try to seed an admin account for the first time the app runs
   AppIdentityDbContext.CreateAdminAccount(app.ApplicationServices, Configuration).Wait();
}


//AppIdentityDbContext.cs
public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
    public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options) : base(options) { }

    public static async Task CreateAdminAccount(IServiceProvider serviceProvider, IConfiguration configuration)
    {
        UserManager<AppUser> userManager = serviceProvider.GetRequiredService<UserManager<AppUser>>();
        RoleManager<IdentityRole> roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
        string username = configuration["Data:AdminUser:Name"];
        string email = configuration["Data:AdminUser:Email"];
        string password = configuration["Data:AdminUser:Password"];
        string role = configuration["Data:AdminUser:Role"];

        if (await userManager.FindByNameAsync(username) == null)
        {
            if (await roleManager.FindByNameAsync(role) == null)
            {
                await roleManager.CreateAsync(new IdentityRole(role));
            }
            AppUser user = new AppUser
            {
                UserName = username,
                Email = email
            };
            IdentityResult result = await userManager.CreateAsync(user, password);
            if (result.Succeeded)
            {
                await userManager.AddToRoleAsync(user, role);
            }
        }
    }
}

然后教科书上写着:

因为我正在通过 IApplicationBuilder.ApplicationServices 提供程序访问范围服务, 我还必须在 Program 类中禁用依赖注入作用域验证功能,如下所示:

//Program.cs
public static IWebHost BuildWebHost(string[] args) =>
 WebHost.CreateDefaultBuilder(args)
 .UseStartup<Startup>()
 .UseDefaultServiceProvider(options => options.ValidateScopes = false)
 .Build();

我对 DI 有基本的了解,但我对这个例子真的很困惑,以下是我的问题:

Q1- 通过 IApplicationBuilder.ApplicationServices 提供程序访问范围服务 这是什么意思?它试图访问哪些服务?为什么它的作用域不是瞬态的或单例的?

Q2- 为什么我们必须禁用依赖注入作用域验证,作用域验证试图实现什么?

【问题讨论】:

    标签: c# dependency-injection asp.net-core-mvc


    【解决方案1】:

    为了了解发生了什么,您首先必须了解依赖注入生命周期之间的区别:

    • 瞬态:为每个解决的依赖项创建一个新实例。
    • 单例:只要服务被解析,就会使用一个共享实例。
    • 作用域:只要服务在单个作用域(或请求)内得到解析,就会共享单个实例。后续请求将意味着将再次创建一个新实例。

    数据库上下文保存与数据库的连接。这就是为什么您通常不希望它是单例的,这样您就不会在应用程序的整个生命周期中保持单个连接打开。所以你想让它瞬态。但是,如果您需要在服务单个请求时多次访问数据库,您将在短时间内多次打开数据库连接。所以折衷方案是默认使其成为一个作用域依赖:这样你就不会长时间保持连接打开,但你仍然可以在短时间内重用连接。

    现在,让我们考虑一下当单例服务依赖于非单例服务时会发生什么:单例服务只创建一次,因此它的依赖关系也只解析一次。这意味着它所拥有的任何依赖关系现在都可以在该服务的整个生命周期(即应用程序的生命周期)中有效地共享。因此,通过依赖非单件服务,您可以有效地使这些服务成为准单件服务。

    这就是为什么有一个保护在起作用(在开发过程中),它可以保护你不犯这个错误:范围验证将检查你是否依赖于范围之外的范围服务,例如在单例服务中。这样,您就不会逃避该范围服务的期望生命周期。

    当您现在在 Configure 方法中运行 AppIdentityDbContext.CreateAdminAccount 时,您是在范围之外运行它。所以你基本上在“单身土地”之内。您现在创建的任何依赖项都将保留。由于您解析了都依赖于作用域数据库上下文的 UserManager&lt;AppUser&gt;RoleManager&lt;IdentityRole&gt;,因此您现在正在转义数据库上下文的已配置作用域生命周期。

    为了解决这个问题,您应该创建一个短期作用域,然后您可以在其中访问作用域服务(因为您在作用域内),当作用域终止时,这些服务将被正确清理:

    public static async Task CreateAdminAccount(IServiceProvider serviceProvider, IConfiguration configuration)
    {
        // get service scope factory (you could also pass this instead of the service provider)
        var serviceScopeFactory = serviceProvider.GetService<IServiceScopeFactory>();
    
        // create a scope
        using (var scope = serviceScopeFactory.CreateScope())
        {
            // resolve the services *within that scope*
            var userManager = scope.ServiceProvider.GetRequiredService<UserManager<AppUser>>();
            var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
    
            // do stuff
        }
        // scope is terminated after the using ends, and all scoped dependencies will be cleaned up
    }
    

    【讨论】:

    • 抱歉还是没明白,我现在了解捕获的依赖关系。但是AppIdentityDbContext,UserManager,RoleManager都注册了scoped life time,应该没有问题吧?
    • 但是您在Configure 中解析这些服务,它执行一次并且在范围之外执行。因此服务已解决,但从未正确清理。
    • 我想我对基础部分感到困惑,我理解Configure中的这些服务执行一次,但是为什么这些服务超出了范围?
    • 除此问题外,我还发布了一个新问题,您能看看吗? stackoverflow.com/questions/57666457/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-17
    • 2017-03-20
    • 2018-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多