Dynamically update config in ASP.NET Core 5 application

1.2k Views Asked by At

I have an application running on .NET 5. We have a configuration file which looks like this

{
   "Config1" : {
     "k1" : "v1",
     "k2" : "v2"
    ...........
   },
   "Config2" : {
     "k3" : "v3",
     "k4" : "v4"
   }
}

Using a singleton object to load the configuration as

services.Configure<Config1>(
                this.Configuration.GetSection(nameof(Config1)));
services.Configure<Config2>(
                this.Configuration.GetSection(nameof(Config2)));

And these objects are injected into calling function using constructor.

public claas C1
{
    public IOptionsMonitor<Config1> 

    public C1(IOptionsMonitor<Config2> config2, IOptionsMonitor<Config1> config1) 
    {
        this.config2 = config2;
        this.config1 = config1;
    }
}

Now we need to add a new configuration in the config file which should override some of the existing configs based on the payload from user.

So Config3 object will look like

"Config3" : {
  "userDefinedprop1" : {
     "Config2" : {
        "k4" : "v4"
     }
  },
  "userDefinedprop2" : {
     "Config1" : {
        "k1" : "v1"
     }
  }
   
}

I want to override some of the existing properties in config1 and config2 based on user input for each incoming request i.e during runtime. How should I approach this without breaking the existing functionality.

1

There are 1 best solutions below

3
Dave Thieben On

First of all, make sure you understand very well the Configuration Provider model used in ASP.NET Core. This Provider model allows you to add additional Providers to the "source of truth" for the application's Configuration, which is what your Options Models are bound to.

Normally you can add static data as an "in memory collection" using an existing provider:

builder.WebHost.ConfigureAppConfiguration(configuration =>
{
    configuration.AddInMemoryCollection(new Dictionary<string, string>() {
        { "key1", "value1" }, { "key2", "value2" },
    });
});

But obviously this will not be updateable nor respond to any changes. ASP.NET Core doesn't have a Provider that does this, so you will need to write your own and add it to the application's Configuration.

In order to send updates to the Provider, we'll need an interface that can be injected into our Controllers:

public interface IUserConfigurationSource
{
    void Set(string key, string value);
}

And then the Provider itself can use the existing ConfigurationProvider base class to build off of:

public class UserConfigurationSource : ConfigurationProvider, IConfigurationSource, IUserConfigurationSource
{
    IConfigurationProvider IConfigurationSource.Build(IConfigurationBuilder builder) => this;

    public override void Set(string key, string value)
    {
        base.Set(key, value);
        OnReload();
    }
}

a couple notes: IConfigurationSource is the interface required to add a Provider to the application. the ConfigurationProvider base class already has a Dictionary we can use to store data in and a "reload token" we can use to trigger update notifications. however we need to override the Set() method to trigger the reload token so consumers will be notified.

and finally register your new provider in 2 places. 1 - add it as a Singleton to dependency injection so it can be consumed in Controllers:

var userConfigurationSource = new UserConfigurationSource();
services.AddSingleton<IUserConfigurationSource>(userConfigurationSource);

2 - add the instance as a Configuration "Source" to the Configuration subsystem:

builder.WebHost.ConfigureAppConfiguration(configuration =>
{
    configuration.Add(userConfigurationSource);
});

Now you can inject IUserConfigurationSource into a Controller and update the data when the users change it:

public ActionResult Index(string count)
{
    _userConfigurationSource.Set("count", count);
    return View();
}

and your use of IOptionsMonitor will be able to access the updated values:

    config1.CurrentValue

however, note that if you have nested config keys, you'll need to use a colon (":") to list the full path of the config key in order for it to be bound to the Options correctly, so for your example config you might have:

    _userConfigurationSource.Set("Config3:userDefinedprop1:Config2:k4", "v4");

good luck!