EF Core миграции не может использовать секретный менеджер

Когда я создаю основные веб-приложения.net, я использую секретный менеджер во время тестирования. Как правило, я могу создать новый веб-проект (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)
{
}

2 ответа

Решение

В настоящее время я тоже сталкиваюсь с этой проблемой. Я придумала решение, которое работает на данный момент, но в лучшем случае можно считать грязным.

Я создал класс конфигурации, который предоставляет интерфейс конфигурации по запросу:

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"],

Однако, если вы хотите получить доступ к секретам приложения, например, из файла миграции, который существует за пределами самого веб-приложения, вам необходимо придерживаться первоначального ответа, поскольку нет простого (насколько мне известно) способа доступа к ним. в противном случае секреты (один из вариантов использования, о котором я могу подумать, это заполнение базы данных администратором и мастер-паролем).

Чтобы использовать миграции в 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);
    }
}

Поскольку я заметил, что многие люди попадают в эту путаницу, я пишу упрощенную версию этой резолюции.

Проблема / путаница

Секретный менеджер в ядре.net предназначен для работы только в среде разработки. При запуске вашего приложения ваш файл launchSettings.json гарантирует, что вашASPNETCORE_ENVIRONMENTпеременная установлена ​​в "Развитие". Однако при запуске миграции EF этот файл не используется. В результате, когда вы запускаете миграции, ваше веб-приложение не запускается в среде разработки и, следовательно, не имеет доступа к секретному менеджеру. Это часто вызывает путаницу относительно того, почему миграции EF не могут использовать секретный менеджер.

Разрешение

Убедитесь, что для переменной среды "ASPNETCORE_ENVIRONMENT" на вашем компьютере установлено значение "Разработка".

Способ использования .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");

                //Console.WriteLine(connectionString);

                var builder = new DbContextOptionsBuilder<AppDbContext>();


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

Объяснение:

Secret Manager предназначен только для разработки, поэтому это не повлияет на миграцию в случае, если он находится в конвейере на этапах QA или Production, поэтому для исправления этого мы будем использовать строку подключения разработчика, которая существует appsettings.Development.json в течение #if Debug.

Преимущество использования этого способа заключается в разделении ссылок на класс Startup веб-проекта при использовании библиотеки классов в качестве инфраструктуры данных.