Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update values into appsetting.json?

I am using the IOptions pattern as described in the official documentation.

This works fine when I am reading values from appsetting.json, but how do I update values and save changes back to appsetting.json?

In my case, I have a few fields that can be edited from the user interface (by admin user in application). Hence I am looking for the ideal approach to update these values via the option accessor.

like image 552
439 Avatar asked Dec 05 '16 09:12

439


People also ask

How do I add Appsettings to json?

Add Json File After adding the file, right click on appsettings. json and select properties. Then set “Copy to Ouptut Directory” option to Copy Always. Add few settings to json file, so that you can verify that those settings are loaded.

How does Appsettings json work?

The appsettings. json file is generally used to store the application configuration settings such as database connection strings, any application scope global variables, and much other information.

How do I get Appsettings json value in startup CS?

In order to add AppSettings. json file, right click on the Project in Solution Explorer. Then click Add, then New Item and then choose App Settings File option (shown below) and click Add button. Once the File is created, it will have a DefaultConnection, below that a new AppSettings entry is added.

How does json define connections string in Appsettings?

To define the connection strings in appsettings. json it is important to specify it in the right section of the JSON structure. Now we can read it in our code by calling the GetConnectionString method in the Microsoft. Extensions.


1 Answers

At the time of writing this answer it seemed that there is no component provided by the Microsoft.Extensions.Options package that has functionality to write configuration values back to appsettings.json.

In one of my ASP.NET Core projects I wanted to enable the user to change some application settings - and those setting values should be stored in appsettings.json, more precisly in an optional appsettings.custom.json file, that gets added to the configuration if present.

Like this...

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(); } 

I declared the IWritableOptions<T> interface that extends IOptions<T>; so I can just replace IOptions<T> by IWritableOptions<T> whenever I want to read and write settings.

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

Also, I came up with IOptionsWriter, which is a component that is intended to be used by IWritableOptions<T> to update a configuration section. This is my implementation for the beforementioned interfaces...

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();     } } 

Since the writer is not aware about the file structure, I decided to handle sections as JObject objects. The accessor tries to find the requested section and deserializes it to an instance of T, uses the current value (if not found), or just creates a new instance of T, if the current value is null. This holder object is than passed to the caller, who will apply the changes to it. Than the changed object gets converted back to a JToken instance that is going to replace the section...

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);         });     } } 

Finally, I implemented an extension method for IServicesCollection allowing me to easily configure a writable options accessor...

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);         });     } } 

Which can be used in ConfigureServices like...

services.ConfigureWritable<CustomizableOptions>(this.Configuration,      "MySection", "appsettings.custom.json"); 

In my Controller class I can just demand an IWritableOptions<CustomizableOptions> instance, that has the same characteristics as IOptions<T>, but also allows to change and store configuration values.

private IWritableOptions<CustomizableOptions> options;  ...  this.options.Update((opt) => {     opt.SampleOption = "..."; }); 
like image 56
Matze Avatar answered Sep 20 '22 16:09

Matze