13

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

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

该网站可以正常工作,并且可以很好地连接到数据库。但是,当我尝试使用诸如 ef 核心迁移命令时add-migration,它们似乎无法从秘密管理器访问连接字符串。我收到错误消息“连接字符串不能为空”。当我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" />

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

更新

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

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

4 回答 4

9

我目前也遇到了这个确切的问题。我想出了一个目前可行的解决方案但充其量可能会认为是混乱的。

我创建了一个配置类,它在请求时提供配置接口:

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 脚本,并且它可以工作(您显然不希望保留脚本,因为它会暴露实际的机密)。

更新

上述配置也可以在方法中设置到 Program.cs 类中BuildWebHost

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

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

或者如果使用该约定,则在启动构造函数中

更新 2(解释)

事实证明,这个问题是因为迁移脚本在环境设置为“生产”的情况下运行。秘密管理器预设为仅在“开发”环境中工作(有充分理由)。该.AddUserSecrets<Startup>()函数只是添加了所有环境的秘密。

为确保未将其设置为您的生产服务器,我注意到有两种解决方案,此处建议一种:https ://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell

运行前设置 env:ASPNETCORE_ENVIRONMENT 以指定 ASP.NET Core 环境。

此解决方案意味着将来无需.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 应用程序本身之外的迁移文件访问应用程序机密,则需要遵守原始答案,因为没有简单的方法(据我所知)访问这些否则秘密(我能想到的一个用例是使用管理员和主密码为数据库播种)。

于 2018-02-19T19:13:17.163 回答
5

要在 NetCore 中使用带有用户机密的迁移,我们还可以设置一个类 (SqlContextFactory) 以使用指定的配置构建器创建它自己的 SqlContext 实例。这样我们就不必在我们的 Program 或 Startup 类中创建某种解决方法。在下面的示例SqlContext中是DbContext/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);
    }
}
于 2018-07-18T10:00:03.840 回答
2

由于我注意到很多人遇到了这种困惑,所以我正在编写这个决议的简化版本。

问题/困惑

.net 核心中的秘密管理器被设计为仅在开发环境中工作。运行您的应用程序时,您的 launchSettings.json 文件可确保您的ASPNETCORE_ENVIRONMENT变量设置为“开发”。但是,当您运行 EF 迁移时,它不会使用此文件。因此,当您运行迁移时,您的 Web 应用程序不会在开发环境中运行,因此无法访问密钥管理器。这通常会导致混淆为什么 EF 迁移不能使用秘密管理器。

决议

确保您的环境变量“ASPNETCORE_ENVIRONMENT”在您的计算机中设置为“开发”。

于 2020-06-15T20:28:33.587 回答
1

.AddUserSecrets<Startup>()如果我们将 DbContext 放在单独的类库中并使用 DesignTimeFactory,则使用方式将进行循环引用

这样做的干净方法是:

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 或生产阶段将其置于管道中,这不会影响迁移,因此要解决此问题,我们将使用存在的 dev 连接字符串在appsettings.Development.json期间#if Debug

使用这种方式的好处是在使用类库作为您的数据基础结构的同时,将引用 Web 项目 Startup 类解耦。

于 2020-04-05T14:57:35.580 回答