【问题标题】:ef core migration can't use secret manageref核心迁移不能使用秘密管理器
【发布时间】:2018-07-23 07:37:58
【问题描述】:

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

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

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

【问题讨论】:

    标签: visual-studio-2017 asp.net-core-2.0 entity-framework-core-migrations secret-manager


    【解决方案1】:

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

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

    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.cs类中:

    var config = new ConfigurationBuilder().AddUserSecrets<Startup>().Build();
    
    return WebHost.CreateDefaultBuilder(args).UseConfiguration(config)...Build()
    

    如果使用that Convention,则在启动构造函数中

    更新 2(说明)

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

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

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

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

    第二种解决方案是仅在调试构建时设置.AddUserSecrets&lt;Startup&gt;(),如下所示:

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

    【讨论】:

    • 我没有明白var secret = conf["Secret"]; 的意思。我的意思是,如果您已经获得了迁移文件,这意味着迁移已经可以访问您的秘密,对吧?
    • 哦,是的,抱歉,我只是试图传达您访问它的方式。就像您已经使用密钥“秘密”存储了一个秘密。
    • 嗯,很奇怪,尤其是因为 CLI 无法识别上下文。也许你可以尝试搭建一个现有的数据库(这样 CLI 就会知道要引用什么。)其他一切看起来都和我的完全一样。我只得到错误'值不能为空。当我从 ConfigurationBuilder 中取出 'AddUserSecrets' 时,参数名称:connectionString'。您是否将该语句放入迁移无法访问的某种条件子句中?
    • 很高兴为您服务!
    • 是的,谢谢。顺便说一下,所有配置通常都在 startup.cs 类中处理。如果您想在其他地方访问它,建议您通过依赖注入使用服务
    【解决方案2】:

    要在 NetCore 中使用用户机密进行迁移,我们还可以设置一个类 (SqlContextFactory) 以使用指定的配置构建器创建它自己的 SqlContext 实例。这样我们就不必在我们的 Program 或 Startup 类中创建某种解决方法。在下面的示例中,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);
        }
    }
    

    【讨论】:

    • 我没有明白这一点。你只是在一个全新的班级上做同样的“某种工作”。这意味着你只是有额外的代码。此外,program.cs 中的代码对于将来的使用都是必需的。
    • 您可以查看此解决方案的简化版本:stackoverflow.com/questions/50104872/…
    • 这对我来说似乎是一个非常圆滑的建议,除非现在有更好的方法?首先需要这个类来创建迁移,所以为什么不让它更有用,做点别的,在启动文件中节省混乱。
    【解决方案3】:

    由于我注意到很多人遇到这种困惑,我正在编写此解决方案的简化版本。

    问题/困惑

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

    决议

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

    【讨论】:

    • 感谢您的整理!尽可能简单!但是上面的解决方案也有贡献!
    • 是的,很详细,帮我找到了解决办法。
    【解决方案4】:

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

    这样做的干净方法是:

    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 或生产阶段的管道中,这不会影响迁移,因此我们将使用 @987654323 中存在的开发连接字符串来解决此问题@期间#if Debug

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-05-15
      • 2018-05-21
      • 1970-01-01
      • 2019-06-18
      • 2018-10-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多