How to modify values in the Options object (holds configuration settings) after it has been registered with dependency injection in ASP.NET Core?

Viewed 1393

I have a method, one of many in different classes, example below, in a .NET class which uses a connection string, a property in an options object (Options Pattern), called DBOptions, which was injected into the class.

DBOptions was registered in DI during app startup using services.Configure<DBOptions>().

DBOptions during app startup is configurated to use a specific named connectionstring from appsettings.json.

There's a need now to conditionally be able during run time to use another connection string. This is per web request by a user selection in the UI. I added a second named connection string in appsettings.json.

Since currently during app startup DBoptions is set to use a specific named conenctionstring, how do I switch between the two connectionstrings during run time before DI injects DBOptions in a class? Or how do I update the DBOptions connectionstring before it gets injected into any class that uses it? AFAIK IOptionsSnapshot reloads the options when the source (appsettings.json file) gets modified which is not my case.

App is not using EFCore.

public class Books
{
    private readonly string connectionString;
    
    public Books(IOptions<DBOptions> options)
    {
        this.connectionString = options.Value.connectionString; //<-- value needs to be set conditionally
                                                    //outside of any class that uses DBoptions
    }
    public void SomeMethod()
    {
        ....  //uses connectionString
    }
}       
1 Answers

Since you indicated that the options would be request dependent then you can access the IHttpContextAccessor and extract the conditions needed from the request to set the desired member.

Here is an oversimplified example

//...

services.AddOptions<DBOptions>()
    .Configure<IHttpContextAccessor, IConfiguration>(
        (options, http, config) => {
            HttpContext context = http.Current;
            HttpRequest request = context.Request;

            if({/*some condition based on request...*/}) {
                options.connectionString = config.GetConnectionString("name1");
            } else {
                options.connectionString = config.GetConnectionString("name2");
            }

            //...etc
        };

//...

Reference: Use DI services to configure options.

Or forego tightly coupling to the framework's Options pattern and directly register you class as transient or scoped since it is created on a per HTTP request basis

//...

services.AddTransient<DBOptions>(sp => {
    IHttpContextAccessor http = sp.GetRequiredService<IHttpContextAccessor>();
    IConfiguration config = sp.GetRequiredService<IConfiguration>();

    HttpContext context = http.Current;
    HttpRequest request = context.Request;

    DBOptions options = new DBOptions() {
        //populate as needed or extract from configuration
    }

    if({/*some condition based on request...*/}) {
        options.connectionString = config.GetConnectionString("name1");
    } else {
        options.connectionString = config.GetConnectionString("name2");
    }

    //...etc

    return options;
});

//...

This would however require dependent class to explicitly depend on DBOptions instead of tightly coupling to IOptions<DBOptions>

public class Books {
    private readonly string connectionString;
    
    public Books(DBOptions options) {
        this.connectionString = options.connectionString; 
    }

    public void SomeMethod() {
        //... uses connectionString
    }
}       
Related