Store and retrieve settings using IOptions pattern in C#

Jun 23, 2024ยท

5 min read

Introduction - Importance of managing settings in applications

Storing and reading settings/configurations is a common scenario in any application. General examples include connection string, app settings, etc.

Reading such settings should be easy, strongly typed, easy to maintain, and less room for error. In this blog post, I will show how to achieve this using IOptions<> pattern.

Ways to store settings in .NET

Commonly you can store settings in the following places in .NET

  • appsettings.json

  • user secrets (secrets.json)

  • environment variables

I have created a minimal API project using Visual Studio with default settings for this blog post.

Retrieve settings using IConfiguration

appsettings.json

Create a new section in appsettings.json with the following data.

 "settings": {
   "key1" :  "value1"
 }

Inject IConfiguration delegate to the API method and retrieve the above value by calling configuration.GetValue("settings:key1").

Environment variables

You can also retrieve the value from the environment file in the same way above. The project has launchSettings.json file where you can add environment settings under profiles --> https (any profile you used to run). Get the value by calling configuration.GetValue("key2").

 "environmentVariables": {
   "ASPNETCORE_ENVIRONMENT": "Development",
   "settings:key2": "value2"
 }
๐Ÿ’ก
Environment variables only support Key/Value pairs. Hence colon ":" is used to create sections.

User secrets

You can store sensitive data in user secrets during development, this will be stored in %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json

You can create the above file using either running dotnet user-secrets init in the project path or right-click on the project and click "manage user secrets".

For this current example, please add the below section to the secrets.json file.

"settings": {
  "key3": "value3"
}

When to use user secrets? If you want to store database credentials, API keys, or other sensitive information that should not be part of the git repository, you can use user secrets. This is particularly useful for public repositories.

You can retrieve the user secrets in the same way as above configuration.GetValue("settings:key3")

app.MapGet("/settings", (IConfiguration configuration) => new
    {
        fromAppSetting = configuration.GetValue<string>("settings:key1"),
        FromEnv = configuration.GetValue<string>("settings:key2"),
        FromUserSecrets = configuration.GetValue<string>("settings:key3"),
    })
.WithName("GetSettings")
.WithOpenApi();

IOptions fetch value

Retrieve settings using IOptions pattern

What is the IOptions Pattern?

The IOptions pattern in .NET Core is a design approach for handling configuration settings in a type-safe way. It allows binding configuration sections to strongly typed classes and injecting these classes into application components. This pattern offers benefits like type safety, validation, and the ability to reload settings when they change.

Implement IOptions pattern in asp.net core C

Consider the above appsettings.json example. Create a class for the settings as below.

public class Settings
{
    public string Key1 { get; set; }
}

Next register IOptions services with the above Settings class.

builder.Services.AddOptions<Settings>().BindConfiguration(nameof(Settings));

The above code registers a new Settings instance as a singleton and binds the settings section data from configuration (app settings, user secrets, or environment variables).

You can inject IOptions<Settings> to anywhere in the application to access the settings. Check the updated code below.

app.MapGet("/settings", (IConfiguration configuration, IOptions<Settings> options) => new
    {
        fromAppSetting = configuration.GetValue<string>("settings:key1"),
        FromEnv = configuration.GetValue<string>("key2"),
        FromUserSecrets = configuration.GetValue<string>("key3"),
        FromOptions = options.Value.Key1,
        FromOptions2 = options.Value.Key2,
        FromOptions3 = options.Value.Key3
    })
.WithName("GetSettings")
.WithOpenApi();

By calling the above method, returns the setting value from appsettings.json but in a strongly typed way.

IOptions fetch value

Please note that each key/value pair comes from different configurations but IOptions pattern still recognizes the same and combines all the values into a single section.

Settings validations in IOptions pattern - ValidateDataAnnotations

You can validate settings using data annotations in the settings class as below. Here we have added Required condition the Key1 which is validated against the empty value.

public class Settings
{
    [Required(AllowEmptyStrings = false)]
    public string Key1 { get; set; }
    public string Key2 { get; set; }
    public string Key3 { get; set; }
}

You should also add ValidateDataAnnotations to the options pattern registration to make the validations possible.

builder.Services.AddOptions<Settings>()
    .BindConfiguration(nameof(Settings))
    .ValidateDataAnnotations();

Now update the Key1 as empty value, build the project, and navigate to the above endpoint. This will throw the validation error

data annotation validation error

You can see here, the validation is only happening when you are trying to access the settings and not on application startup.

ValidateOnStart

You can add ValidateOnStart() to the above options pattern, this enforces validation check on application startup instead of runtime. This will be useful to avoid errors at runtime and we can avoid any surprises at a later time.

If validation fails, the application won't start and throws the actual validation error as above.

IOptions error screen shot

Using IOptionsSnapshot and IOptionsMonitor

Since IOptions pattern is reading data from configuration only on application startup, later changes won't be available unless you restart the application.

So, for reading the latest available settings from the configuration, we have two interfaces IOptionsSnapshot and IOptionsMonitor.

IOptionsSnapshot - It's registered as scoped, and the values are fetched from the configuration whenever a request starts. If the settings are changed during the request process, it won't be available for the same request but available for the next request. Options snapshots are designed for use with transient and scoped dependencies.

IOptionsMonitor - It's registered as a singleton and always fetches the latest value from the configuration. It is beneficial in singleton dependencies.

References

Options Pattern In .NET 6.0 (c-sharpcorner.com)

How to use the IOptions pattern for configuration in ASP.NET Core RC2 (andrewlock.net)

Using the IOptions Pattern in a .NET Core Application โ€“ andrewhalil.com

Introduction to IOptions Pattern in .Net. - DEV Community

OPTIONS PATTERN IN .NET 6 - DEV Community

Options Pattern in ASP.NET Core โ€“ Bind & Validate Configurations from appsettings.json - codewithmukesh