Using Options pattern on ASP.NET

Using Options pattern on ASP.NET

In practically every project or application, there is a requirement to configure and modify various settings based on the environment being operated on. A prime example of this is handling connection strings for databases, which should ideally remain confidential and not be exposed. Other types of configurations could be paths, default values, user secrets, et cetera. For .NET and ASP.NET, these settings could be stored in environmental variables, appsettings.json, user secrets, a key vault (like Azure Key Vault), databases, or other custom settings files.

.NET provides the IOptions interface to be able to use strongly typed configuration values from the aforementioned files above. This way, we can also observe the Options pattern.

Options Pattern

According to Microsoft, the options pattern utilizes classes to provide strongly-typed access to groups of related configuration values. When these values are isolated by scenario into separate classes, the application would adhere to two important software engineering principles:

  • Interface Segregation Principle (ISP): Classes that depend on configuration settings depend only on configuration settings that they use.

  • Separation of Concerns: Settings for different parts of the application are not dependent or tightly coupled with each other.

Configuration Binding

Let's say we would like to access the configuration values under LongPollingOptions from an appsettings.json.

{
  "LongPollingOptions": {
    "AutoRetry": true,
    "IntervalSeconds": 3000
  },
  "DatabaseLogLevel": "Warning"
}

We should create a strongly typed options class that matches the section of the configuration file.

public sealed class LongPollingOptions
{
    public bool AutoRetry { get; set; }
    public double IntervalSeconds { get; set; }
}

To follow the options pattern, the options class must:

  • Built as non-abstract with a public constructor with no parameters
  • Contain public read-write properties to bind (fields are not bound)

We can now bind the configuration values to the options class. In Program.cs, insert the following code after declaring and initializing the WebApplication builder.

// Program.cs
// Declare and initialize WebApplication builder
var builder = WebApplication.CreateBuilder(args);

// Insert following code to bind configuration data
builder.Services.Configure<LongPollingOptions>(builder.Configuration.GetSection("LongPollingOptions"));

Let's discuss the code that was inserted bit by bit.

builder.Configuration.GetSection("LongPollingOptions")

The preceding code retrieves a configuration section by its name, LongPollingOptions as specified here.

builder.Services.Configure<LongPollingOptions>(builder.Configuration.GetSection("LongPollingOptions"));

In the preceding code, the LongPollingOptions configuration section that was retrieved is bound to the LongPollingOptions instance. This hydrates the C# LongPollingOptions object properties with corresponding values from the configuration. It is then added to the dependency injection service container.

That's it! The newly created strongly typed LongPollingOptions options could now be accessed anywhere in the application that need to call it.

Access the options

The following code demonstrates how we can access the options in our classes.

public sealed class ExampleService
{
  private readonly LongPollingOptions _options;

  public ExampleService(IOptions<LongPollingOptions> options) =>
      _options = options.Value;

  public void DisplayValues()
  {
      Console.WriteLine($"LongPollingOptions.AutoRetry={_options.AutoRetry}");
      Console.WriteLine($"LongPollingOptions.IntervalSeconds={_options.IntervalSeconds}");
  }
}