appsettings.json в конфигурации ASP.NET Core 2.0 Preview GetSection null

Я пытался вызвать GetSection из инъецированной конфигурации в Startup.cs. Значение было null, в то время как indexer для конкретного раздела возвращает non-null значение. Мне кажется, ошибка в методе GetSection или я ошибаюсь?

appsettings.json:

{"MyConfig": {"ConfigA": "valueA", "ConfigB": "valueB"}}

Program.cs:

    public static void Main(string[] args)
    {
        var host = BuildWebHost(args);
        host.Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();

Startup.cs:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        var mySection = this.Configuration.GetSection("MyConfig");

        var myVal = this.Configuration["MyConfig:ConfigA"];

Ответ 1

Сначала я проверил, есть ли какие-либо изменения между 1.1.1 и 2.x JsonConfigurationProvider.cs, внутренним кодом, который в конечном итоге JsonConfigurationProvider.cs восстановленные значения из вашего файла JSON. Не было никаких изменений в этом или любом другом коде, который в конечном итоге вызван вашим this.Configuration.GetSection("MyConfig"); ,

Способ получения значений работает в том, что Configuration будет искать ваш MyConfig в каждом конфигурационном провайдере в обратном порядке, как определено в коде, до тех пор, пока не будет найдено значение. В вашем примере поставщики (json, envionment variables, args командной строки) содержатся в Webhost.CreateDefaultBuilder() (см. Здесь).

Рассматривая код JsonConfigurationFileParser.cs, он строит Dictionary<string, string> для ключей и значений, но только для примитивных значений. То есть, для MyConfig не сохраняется MyConfig (на этом уровне это объект), но будет ключ для MyConfig:ConfigA, а значения массива будут выглядеть как MyConfig:ConfigA:0, MyConfig:ConfigA:1 и т.д.

Наконец, вы обнаружите, что Configuration.GetSection("MyConfig") всегда возвращает вам новую ConfigurationSection которая никогда не является нулевой, и в худшем случае будет иметь свойство Value null.

Итак, что происходит, когда вы наводите на ConfigurationSection с Intellisense и смотрите на свойство Value это то, что каждый поставщик конфигурации был обыскан, и никто не обнаружил, что у него есть ключ "MyConfig" с примитивным значением, преобразованным в строку для возврата.

Вы, по крайней мере, должны будете позвонить:

services.Configure<MyConfigOptions>(configuration.GetSection("MyConfig"));
services.AddSingleton(cfg => cfg.GetService<IOptions<MyConfigOptions>>().Value);

чтобы он вводился в вашем приложении как объект С#. В противном случае вызовите отдельные значения с синтаксисом разделителя двоеточия ["MyConfig:ConfigA"] или с помощью var mySection = this.Configuration.GetSection("MyConfig")["ConfigA"]; , который является избыточным, но иллюстрирует, что он используется только для извлечения примитивов.

Чтобы связать объекты С# и ввести их, я создал следующий метод расширения:

public static class IServiceCollectionExtensions
{
    public static IServiceCollection AddConfigOptions<TOptions>(this IServiceCollection services,
        IConfiguration configuration, string section) where TOptions : class, new()
    {
        services.Configure<TOptions>(configuration.GetSection(section));
        services.AddSingleton(cfg => cfg.GetService<IOptions<TOptions>>().Value);
        return services;
    }
}

который можно назвать следующим:

public class Startup
{
    public Startup(IConfiguration configuration) => Configuration = configuration;

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddConfigOptions<EmailOptions>(Configuration, "Email")

и вводится следующим образом:

public class EmailSender : IEmailSender
{
    private EmailOptions _emailOptions;

    public EmailSender(EmailOptions options) => _emailOptions = options;

Ответ 2

Я нашел AspNetCore 2.0 намного проще, чем 1.x с точки зрения конфигурации.

Если вы установили Startup(IConfiguration configuration) останова в конструкторе Startup(IConfiguration configuration), вы заметите, что переменная configuration имеет около 5 поставщиков.

JsonConfigurationProvider - это поставщик, который вас интересует для файла appsettings.json, но вы, вероятно, заметите, что свойство Data имеет значение Count=0. Скорее всего, это связано с тем, что ваше приложение ищет файл appsettings.json в каталоге, указанном в JsonConfigurationProvider.Source.FileProvider.Root (который по умолчанию является wwwroot).

Все, что я сделал, это добавить задачу MSBuild в файл.csproj следующим образом:

<Copy SourceFiles="appsettings.json" DestinationFolder="wwwroot" />

И это казалось безупречным.

Это, очевидно, полезно только при локальном развитии. Тем не менее, общая практика в наши дни никогда не будет иметь конфигурацию в файле в первую очередь, тем более, что она в конечном итоге станет частью вашей истории репо. Вместо этого при развертывании вашего приложения две распространенные практики в наши дни используют переменные среды для переопределения ваших значений или даже лучше использовать хранилище ключей/значений, такое как consul.

Более того, я вижу целую кучу причудливых примеров онлайн о том, как использовать services.Configure<>() IConfiguration configuration services.Configure<>() которая в порядке, но Startup.cs уже использует DI для ввода значений в IConfiguration configuration. Это означает, что IConfiguration уже зарегистрирован в.NET core IoC Container, так что по существу означает, что вы уже можете использовать IConfiguration в любом другом классе вашего приложения, например:

public JobsRepository(IConfiguration configuration)
{
    this.configA = configuration.GetSection("MyConfig:ConfigA").Value;
}

Ответ 3

В моем случае у меня отсутствовал пакет:

Microsoft.Extensions.Configuration.Binder

На самом деле это задокументировано как тонкий комментарий кода здесь

Ответ 4

Теперь конфигурация передается в startup на конструкторе. Поскольку он вводится, он может быть предварительно подготовлен, прежде чем он будет предоставлен вам в приложении Program.cs.

Таким образом, вы можете попробовать в программе Program.cs

 public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, builder) =>
        {
            IHostingEnvironment env = context.HostingEnvironment;

            builder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
        })
        .UseStartup<Startup>()
        .Build();

Надеюсь это поможет.

Fabian