Lázár Zsolt
Lázár Zsolt

Reputation: 843

How can I dynamically re-configure a Service during runtime in ASP.NET Core 5?

I am using Google authentication in my web app, and the OAuth keys are currently hard-coded in ConfigureServices:

services.AddAuthentication()
    .AddGoogle(options =>
    {
        options.ClientId = "my-client-id";
        options.ClientSecret = "my-client-secret";
    });

However, I would like to give the site administrator the opportunity to change the ClientId and ClientSecret from the web app's settings page, preferably without having to restart the server.

To do this, I'd have to somehow trigger a re-configuration of the Google service and the GoogleOptions object when the user hits 'Save' on the settings page. This is what I'm having trouble with. Also, I would like to store these settings in an EF Core DbContext, and not in a physical config file.

So far I've tried to move the settings to a separate class that implements IPostConfigureOptions. This should allow me to inject my database context, because based on the documentation, PostConfigure is supposed to run after all other configurations have occurred. The settings are loaded correctly from this new class, but the injection of the DB context fails with the following exception:

System.InvalidOperationException: Cannot consume scoped service 'AppDatabase' from singleton 'IOptionsMonitor`1[GoogleOptions]'

This is weird, because the ConfigureGoogleOptions is registered as Scoped, and not as a Singleton.

Here's my options class:

public class ConfigureGoogleOptions : IPostConfigureOptions<GoogleOptions>
{
    private readonly AppDatabase database;

    public ConfigureGoogleOptions(AppDatabase database)
    {
        this.database = database;
    }
    
    public void PostConfigure(string name, GoogleOptions options)
    {
        options.ClientId = "my-client-id.apps.googleusercontent.com";
        options.ClientSecret = "my-client-secret";
    }
}

And registering it in ConfigureServices:

services.AddScoped<IPostConfigureOptions<GoogleOptions>, ConfigureGoogleOptions>();

Even if the databse injection worked, there's still a second problem. The PostConfigure function in my class only gets called once after the application starts, and never again. I assume that it caches the settings somewhere, and I don't know how to invalidate or disable this cache so I can dynamically provide values.

Short Summary / tl;dr:

I want to load the ClientId and ClientSecret settings of the Google OAuth service from my own database, and I want to be able to change them dynamically while the server is running.

Upvotes: 6

Views: 4563

Answers (1)

King King
King King

Reputation: 63357

Internally the google handler will use IOptionsMonitor<GoogleOptions> to get the GoogleOptions once or until it's reloaded (such as when the options is bound from a configuration file and saving the file will trigger the reloading). The IOptionsMonitor internally will use IOptionsMonitorCache and this cache is registered as singleton. So the options instance you get from IOptionsMonitor<GoogleOptions> is the same (reference) with the AuthenticationHandler<GoogleOptions>.Options which is used for various operations inside the handler. Even other code if using that options should correctly get it from IOptionsMonitor<GoogleOptions>.

So to change the options at runtime, it's just simple like this:

//inject the IOptionsMonitor<GoogleOptions> into _googleOptionsMonitor;
var runtimeOptions = _googleOptionsMonitor.Get(GoogleDefaults.AuthenticationScheme);
//you change properties of runtimeOptions here
//...

The important point here is we need to use GoogleDefaults.AuthenticationScheme as the key to get the correct instance of options. The IOptionsMonitor.CurrentValue will use the default key of Options.DefaultName (which is an empty string).

Upvotes: 5

Related Questions