Options Pattern in ASP.NET
Tags: programming, coding, csharp, dotnet
Published: 2023-05-13 07:30:02

Options pattern is a powerful and versatile configuration method in .NET, particularly in ASP.NET. It allows developers to group related settings into coherent classes, facilitating the management and injection of configurations throughout the application code.

## Implementation

Creating and implementing the Options pattern follows a series of key steps. First, we need to add in the settings file `appsettings.json` or `appsettings.{Environment}.json` the values we need:

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MyOptions": {
    "Options1": "hello world",
    "Options2": 12
  }
}
```

Subsequently, it's necessary to define a class for the settings:

```csharp
public class MyOptions
{
    public string? Option1 { get; set; }
    public int Option2 { get; set; }
}
```

These settings must be configured in the `Program.cs` file:

```csharp
builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("MyOptions"));
```

Finally, the settings can be injected into the necessary parts of the application via dependency injection.

```csharp
public class MyService
{
    private readonly MyOptions _myOptions;

    public MyService(IOptions<MyOptions> myOptionsAccessor)
    {
        _myOptions = myOptionsAccessor.Value;
    }
}
```

## Read the configuration settings

To access configuration settings, you can use one of these two methods:

1. `GetSection(string key)`: Returns an `IConfigurationSection` that represents a specific part of the configuration settings from the provided key. If the section isn't found in the configuration settings, this method will not throw an exception but rather will return a section with no value.
2. `GetRequiredSection(string key)`: Similar to `GetSection`, it also returns an `IConfigurationSection` representing a specific part of the configuration settings from the provided key. However, if the section isn't found in the configuration settings, `GetRequiredSection` will throw an exception (`InvalidOperationException`).

## IOptions, IOptionsSnapshot, and IOptionsMonitor

Once the Options pattern is implemented, there are three main interfaces that can be used to access the options: `IOptions<T>`, `IOptionsSnapshot<T>`, and `IOptionsMonitor<T>`. Each of these interfaces has a specific use case and provides unique advantages.

- **IOptions<T>**: This is the simplest way to access the options. The `IOptions<T>` instance is created at the application's start and remains the same for the duration of the application. It does not detect changes to the configuration settings during the application's runtime.
- **IOptionsSnapshot<T>**: This interface is particularly useful in ASP.NET applications that handle multiple simultaneous requests. `IOptionsSnapshot<T>` creates a new instance of the options for each request, meaning it can detect changes to configuration settings between different requests.
- **IOptionsMonitor<T>**: `IOptionsMonitor<T>` is similar to `IOptionsSnapshot<T>` in that it can detect changes to configuration settings. However, unlike `IOptionsSnapshot<T>`, it does not create a new instance of the options for each request. Instead, it uses a notification mechanism to update the options when it detects a change. This makes it more efficient than `IOptionsSnapshot<T>` when settings change rarely.

The comparison between these three interfaces fundamentally comes down to the frequency of changes to configuration settings and the need to detect these changes. If your settings never change during the application's runtime, `IOptions<T>` is probably the best choice. If your settings change frequently and you need to detect these changes for every request, then `IOptionsSnapshot<T>` might be the best choice. If your settings change but not very frequently, `IOptionsMonitor<T>` offers a good balance between updating settings and efficiency.

## Benefits

Options pattern offers some advantages:

- **It brings together related settings**: Settings that are logically related can be grouped together in a single class. This makes the code more readable and easier to manage.
- **Provides strong typing of settings**: Settings are strongly typed, which means you will get IntelliSense support and type checks at compile time.
- **Facilitates testing**: Settings can be easily faked in tests, which makes it easier to write unit tests.

## Named Options and Post-configuration

To handle more complex configurations, the Options pattern also provides support for named options and post-configuration.

**Named options** allow for configuring different instances of the same options class. This is particularly useful if you need to use different sets of configurations within the same application.

```csharp
builder.Services.Configure<MyOptions>("option1", builder.Configuration.GetSection("Option1"));
builder.Services.Configure<MyOptions>("option2", builder.Configuration.GetSection("Option2"));
```

Named options can then be retrieved using `IOptionsSnapshot<T>` or `IOptionsMonitor<T>`.

```csharp
public class MyService
{
    private readonly MyOptions _option1;
    private readonly MyOptions _option2;

    public MyService(IOptionsSnapshot<MyOptions> options)
    {
        _option1 = options.Get("option1");
        _option2 = options.Get("option2");
    }
}
```

**Post-configuration** provides the ability to configure the options after they have been initialized within the `Program.cs` file. This is useful if you need to set some options based on others.

```csharp
builder.Services.PostConfigure<MyOptions>(options =>
{
    if (string.IsNullOrEmpty(options.Option1))
    {
        options.Option1 = "Default Value";
    }
});
```

In this example, if `Option1` has not been configured, it is set to "Default Value".

## Options validation

Options pattern supports options validation. You can create a validation class by implementing the `IValidateOptions` interface. For instance:

```csharp
using Microsoft.Extensions.Options;

public class MyOptionsValidation : IValidateOptions<MyOptions>
{
    public ValidateOptionsResult Validate(string name, MyOptions options)
    {
        return string.IsNullOrEmpty(options.Option1) 
            ? ValidateOptionsResult.Fail("Option1 is required.") 
            : ValidateOptionsResult.Success;
    }
}
```

Then, you can register this validation class in the `Program.cs` file:

```csharp
using Microsoft.Extensions.Options;

builder.Services.AddSingleton<IValidateOptions<MyOptions>, MyOptionsValidation>();
```

If the settings do not pass validation, the application throws an exception at startup.

## Conclusion

Options pattern in ASP.NET provides a powerful configuration method. It allows grouping settings into strongly typed classes, offers the ability to monitor changes to settings, supports settings validation, and more. Depending on your specific needs, `IOptions<T>`, `IOptionsSnapshot<T>` and `IOptionsMonitor<T>` offer various ways to handle and access your configuration settings.

## References

- [Options pattern in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0)