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