На момент написания этого ответа казалось, что компонент, предоставленный пакетом Microsoft.Extensions.Options
, имеет функциональные возможности для ввода значений конфигурации обратно в appsettings.json
.
В одном из моих проектов ASP.NET Core
Я хотел, чтобы позволить пользователю изменять некоторые настройки приложения - и те значения настройки должны быть сохранены в appsettings.json
, более высокоточные в необязательном appsettings.custom.json
файле, который будет добавлен к конфигурации, если присутствуют.
Как это ...
public Startup(IHostingEnvironment env)
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.custom.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
Я объявил интерфейс IWritableOptions<T>
, который расширяет IOptions<T>
; поэтому я могу просто заменить IOptions<T>
на IWritableOptions<T>
всякий раз, когда я хочу читать и писать настройки.
public interface IWritableOptions<out T> : IOptions<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
Кроме того, я придумал IOptionsWriter
, который является компонентом, который предназначен для использования IWritableOptions<T>
обновить раздел конфигурации. Это моя реализация для beforementioned интерфейсов ...
class OptionsWriter : IOptionsWriter
{
private readonly IHostingEnvironment environment;
private readonly IConfigurationRoot configuration;
private readonly string file;
public OptionsWriter(
IHostingEnvironment environment,
IConfigurationRoot configuration,
string file)
{
this.environment = environment;
this.configuration = configuration;
this.file = file;
}
public void UpdateOptions(Action<JObject> callback, bool reload = true)
{
IFileProvider fileProvider = this.environment.ContentRootFileProvider;
IFileInfo fi = fileProvider.GetFileInfo(this.file);
JObject config = fileProvider.ReadJsonFileAsObject(fi);
callback(config);
using (var stream = File.OpenWrite(fi.PhysicalPath))
{
stream.SetLength(0);
config.WriteTo(stream);
}
this.configuration.Reload();
}
}
Поскольку автор не знает о структуре файла, я решил обработать разделы, как JObject
объектов. Аксессор пытается найти запрошенный раздел и десериализует его на экземпляр T
, использует текущее значение (если не найдено) или просто создает новый экземпляр T
, если текущее значение составляет null
. Этот объект-держатель передается вызывающему абоненту, который будет применять изменения к нему. Чем измененный объект преобразуется обратно в JToken
экземпляр, который собирается заменить раздел ...
class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly string sectionName;
private readonly IOptionsWriter writer;
private readonly IOptionsMonitor<T> options;
public WritableOptions(
string sectionName,
IOptionsWriter writer,
IOptionsMonitor<T> options)
{
this.sectionName = sectionName;
this.writer = writer;
this.options = options;
}
public T Value => this.options.CurrentValue;
public void Update(Action<T> applyChanges)
{
this.writer.UpdateOptions(opt =>
{
JToken section;
T sectionObject = opt.TryGetValue(this.sectionName, out section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) :
this.options.CurrentValue ?? new T();
applyChanges(sectionObject);
string json = JsonConvert.SerializeObject(sectionObject);
opt[this.sectionName] = JObject.Parse(json);
});
}
}
Наконец, я реализовал метод расширения для IServicesCollection
позволяет мне легко настроить записываемые опции аксессора ...
static class ServicesCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationRoot configuration,
string sectionName,
string file) where T : class, new()
{
services.Configure<T>(configuration.GetSection(sectionName));
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
IOptionsWriter writer = new OptionsWriter(environment, configuration, file);
return new WritableOptions<T>(sectionName, writer, options);
});
}
}
который может быть использован в ConfigureServices
как ...
services.ConfigureWritable<CustomizableOptions>(this.Configuration,
"MySection", "appsettings.custom.json");
в моей Controller
CLA ss Я могу просто потребовать экземпляр IWritableOptions<CustomizableOptions>
, который имеет те же характеристики, что и IOptions<T>
, но также позволяет изменять и сохранять значения конфигурации.
private IWritableOptions<CustomizableOptions> options;
...
this.options.Update((opt) => {
opt.SampleOption = "...";
});
Структура обеспечивает общую инфраструктуру для считывания значений конфигурации, а не изменять их. Когда дело доходит до модификации, вам придется использовать поставщика конкретной конфигурации для доступа и изменения базового источника конфигурации. – haim770
«поставщик конкретной конфигурации для доступа и изменения базового источника конфигурации»? Не могли бы вы дать мне некоторые рекомендации для начала? – 439
Каков источник конфигурации, который вы собираетесь изменить? – haim770