ef 核心迁移不能使用秘密管理器

ef core migration can't use secret manager

当我创建 .net 核心 Web 应用程序时,我在测试期间使用了秘密管理器。我通常能够创建一个新的 web 项目(mvc 和 web api),右键单击该项目并 select "manage user secrets"。这将打开一个 json 文件,我在其中添加了秘密。然后我在我的 startup.cs 中使用它,如下所示:

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseMySql(Configuration["connectionString"]));

该网站在这方面运行良好,并且可以很好地连接到数据库。但是,当我尝试使用 add-migration 等 ef 核心迁移命令时,它们似乎无法从秘密管理器访问连接字符串。我收到错误提示 "connection string can't be null"。当我用实际字符串硬编码 Configuration["connectionString"] 时,错误消失了。我已经在线检查并检查了 .csproj 文件,它们已经包含以下行:

<UserSecretsId>My app name</UserSecretsId>

以后:

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />

我需要添加什么以便迁移可以访问连接字符串吗?

更新

上下文中我只有一个构造函数class:

public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options) : base(options)
{
}

我目前也遇到了这个问题。我想出了一个目前 有效 的解决方案,但人们可能认为充其量是混乱的。

我创建了一个配置 Class,它在请求时提供配置界面:

public static class Configuration
{
    public static IConfiguration GetConfiguration()
    {
        return new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", true, true)
            .AddUserSecrets<Startup>()
            .AddEnvironmentVariables()
            .Build();
    }
}

在迁移中,您可以获取配置文件并访问其 UserSecrets,如下所示:

protected override void Up(MigrationBuilder migrationBuilder)
{
    var conf = Configuration.GetConfiguration();
    var secret = conf["Secret"];
}

我已经测试过使用这些用户秘密创建一个 SQL 脚本,并且它有效(您显然不希望保留脚本,因为它会暴露实际秘密)。

更新

以上配置也可以在BuildWebHost方法中设置成Program.csclass:

var config = new ConfigurationBuilder().AddUserSecrets<Startup>().Build();

return WebHost.CreateDefaultBuilder(args).UseConfiguration(config)...Build()

或者在启动构造函数中使用 that Convention

更新2(解释)

原来这个问题是因为迁移脚本在环境设置为“生产”的情况下运行。秘密管理器 pre-set 只能在“开发”环境中工作(有充分的理由)。 .AddUserSecrets<Startup>() 函数只是为所有环境添加秘密。

为确保这不会设置到您的生产服务器,我注意到有两种解决方案,这里推荐一种:https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell

Set env:ASPNETCORE_ENVIRONMENT before running to specify the ASP.NET Core environment.

此解决方案意味着以后无需在计算机上创建的每个项目上设置 .AddUserSecrets<Startup>()。但是,如果您碰巧在其他计算机之间共享此项目,则需要在每台计算机上进行配置。

第二种解决方案是仅在调试版本上设置 .AddUserSecrets<Startup>(),如下所示:

return new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", true, true)
#if DEBUG
    .AddUserSecrets<Startup>()
#endif
    .AddEnvironmentVariables()
    .Build();   

附加信息

配置接口可以在其构造函数中传递给控制器​​,即

private readonly IConfiguration _configuration;
public TestController(IConfiguration configuration)
{
    _configuration = configuration;
}

因此,可以通过访问 _configuration["secret"].

在该控制器中访问任何机密和应用程序设置

但是,如果您想从存在于 Web 应用程序本身之外的 Migration-File 访问应用程序机密,则需要遵循原始答案,因为没有简单的方法(即我知道)以其他方式访问这些秘密(我能想到的一个用例是使用管理员密码和主密码为数据库播种)。

要在 NetCore 中使用用户机密进行迁移,我们还可以设置 class (SqlContextFactory) 以使用指定的配置生成器创建自己的 SqlContext 实例。这样我们就不必在我们的程序或启动 classes 中创建某种解决方法。在下面的例子中 SqlContextDbContext/IdentityDbContext.

的实现
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;

public class SqlContextFactory : IDesignTimeDbContextFactory<SqlContext>
{
    public SqlContext CreateDbContext(string[] args)
    {
        var config = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: false)
            .AddUserSecrets<Startup>()
            .AddEnvironmentVariables()
            .Build();

        var builder = new DbContextOptionsBuilder<SqlContext>();
        builder.UseSqlServer(config.GetConnectionString("DefaultConnection"));
        return new SqlContext(builder.Options);
    }
}

如果我们将 DbContext 放在单独的 class 库中并使用 DesignTimeFactory

,那么使用 .AddUserSecrets<Startup>() 的方式将产生循环引用

干净的方法是:

public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
    public AppDbContext CreateDbContext(string[] args)
    {
        var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
#if DEBUG
                .AddJsonFile(@Directory.GetCurrentDirectory() + 
                            "{project path}/appsettings.Development.json", 
                    optional: true, reloadOnChange: true)
#else
                .AddJsonFile(@Directory.GetCurrentDirectory() + 
                            "{startup project path}/appsettings.json",
                    optional: true, reloadOnChange: true)
#endif
                .AddEnvironmentVariables()
                .Build();
    
    
        var connectionString = configuration.GetConnectionString("DefaultConnection");

        var builder = new DbContextOptionsBuilder<AppDbContext>();

        Console.WriteLine(connectionString);
        builder.UseSqlServer(connectionString);
        return new AppDbContext(builder.Options);
    }
}

解释:
Secret Manager 仅用于开发阶段,因此如果您在 QA 或生产阶段的管道中使用它,这不会影响迁移,因此要解决这个问题,我们将使用 [=] 中存在的开发连接字符串12=] 在 #if Debug.

期间

使用这种方式的好处是在使用 class 库作为您的数据基础设施时,分离引用 Web 项目启动 class。

由于我注意到很多人 运行 对此感到困惑,因此我正在编写此解决方案的简化版本。

Problem/Confusion

.net core 中的秘密管理器旨在仅在开发环境 中工作。当 运行 连接您的应用程序时,您的 launchSettings.json 文件确保您的 ASPNETCORE_ENVIRONMENT 变量设置为 "Development"。但是,当您 运行 EF 迁移时,它不使用此文件。因此,当您 运行 迁移时,您的网络应用程序不会 运行 在开发环境中,因此无法访问秘密管理器。这通常会导致混淆,为什么 EF 迁移不能使用秘密管理器。

决议

确保您的计算机中的环境变量 "ASPNETCORE_ENVIRONMENT" 设置为 "Development"。