2016-12-05 3 views
9

Я использую схему IOptions, как описано in the official documentation.Как обновить значения в appsetting.json?

Это нормально работает, когда я читаю значения с appsetting.json, но как обновить значения и сохранить изменения до appsetting.json?

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

+1

Структура обеспечивает общую инфраструктуру для считывания значений конфигурации, а не изменять их. Когда дело доходит до модификации, вам придется использовать поставщика конкретной конфигурации для доступа и изменения базового источника конфигурации. – haim770

+0

«поставщик конкретной конфигурации для доступа и изменения базового источника конфигурации»? Не могли бы вы дать мне некоторые рекомендации для начала? – 439

+0

Каков источник конфигурации, который вы собираетесь изменить? – haim770

ответ

10

На момент написания этого ответа казалось, что компонент, предоставленный пакетом 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 = "..."; 
}); 
+0

Большое спасибо. У меня есть следующий вопрос относительно ['IConfigurationRoot'] (https://stackoverflow.com/questions/48939567/how-to-access-iconfigurationroot-in-startup-on-net-core-2), возможно, вы знаете Ответ: –

+0

Это еще единственный способ, через год? –

+0

@ JavierGarcíaManzano Я так думаю; есть проблема с GitHub (подана в марте 2018 года), но она была закрыта: https://github.com/aspnet/Home/issues/2973 – Matze

6

Упрощенная версия ответа Matze в:

public interface IWritableOptions<out T> : IOptionsSnapshot<T> where T : class, new() 
{ 
    void Update(Action<T> applyChanges); 
} 

public class WritableOptions<T> : IWritableOptions<T> where T : class, new() 
{ 
    private readonly IHostingEnvironment _environment; 
    private readonly IOptionsMonitor<T> _options; 
    private readonly string _section; 
    private readonly string _file; 

    public WritableOptions(
     IHostingEnvironment environment, 
     IOptionsMonitor<T> options, 
     string section, 
     string file) 
    { 
     _environment = environment; 
     _options = options; 
     _section = section; 
     _file = file; 
    } 

    public T Value => _options.CurrentValue; 
    public T Get(string name) => _options.Get(name); 

    public void Update(Action<T> applyChanges) 
    { 
     var fileProvider = _environment.ContentRootFileProvider; 
     var fileInfo = fileProvider.GetFileInfo(_file); 
     var physicalPath = fileInfo.PhysicalPath; 

     var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath)); 
     var sectionObject = jObject.TryGetValue(_section, out JToken section) ? 
      JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T()); 

     applyChanges(sectionObject); 

     jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject)); 
     File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented)); 
    } 
} 

public static class ServiceCollectionExtensions 
{ 
    public static void ConfigureWritable<T>(
     this IServiceCollection services, 
     IConfigurationSection section, 
     string file = "appsettings.json") where T : class, new() 
    { 
     services.Configure<T>(section); 
     services.AddTransient<IWritableOptions<T>>(provider => 
     { 
      var environment = provider.GetService<IHostingEnvironment>(); 
      var options = provider.GetService<IOptionsMonitor<T>>(); 
      return new WritableOptions<T>(environment, options, section.Key, file); 
     }); 
    } 
} 

Использование:

services.ConfigureWritable<MyOptions>(Configuration.GetSection("MySection")); 

Тогда:

private readonly IWritableOptions<MyOptions> _options; 

public MyClass(IWritableOptions<MyOptions> options) 
{ 
    _options = options; 
} 

Чтобы сохранить изменения в файле:

_options.Update(opt => { 
    opt.Field1 = "value1"; 
    opt.Field2 = "value2"; 
}); 

И вы можете передать пользовательский файл JSON в качестве дополнительного параметра (он будет использовать appsettings.json по умолчанию):

services.ConfigureWritable<MyOptions>(Configuration.GetSection("MySection"), "appsettings.custom.json");