Using the Options Pattern in .NET

Posted by on Wed, Mar 1, 2023

Intro

The Options Pattern is a great way to group related settings for your application that you are probably already using, but there might be some capabilities and best practices that you might not be taking advantage of or aware of.

For those that might not be aware of the Options pattern in .NET, you can have a group of related settings defined in your AppSettings.json file like this:

1{
2  "Storage": {
3    "StorageAccountName": "imgstor123",
4    "BlobContainer": "thumbnails",
5    "Retries": 1
6  }
7}

You can then define a class to represent these settings:

1public class StorageOptions
2{
3    public static string SectionName = "Storage";
4
5    public string StorageAccountName { get; init; }
6    public string BlobContainer { get; init; }
7    public int Retries { get; init; }
8}

You can then load these settings into this class during application startup while registering it for dependency injection like this:

1var config = builder.Configuration();
2
3builder.Services
4    .AddOptions<StoprageOptions>()
5    .Bind(config.GetSection(StorageOptions.SectionName));

And then in your classes, you can simply do this to get access to those settings:

1public class ImageController
2{
3    private readonly StorageOptions _storageOptions;
4
5    public ImageController(IOptions<StorageOptions> storageOptions)
6    {
7        _storageOptions = storageOptions?.Value ?? ArgumentNullException.ThrowIfNull(storageOptions)
8    }
9}

Validating settings

One thing people might not be aware of is that you can actually validate your application settings to make sure that the values provided are what you expect them to be. For instance, you can make sure that settings have values (required), are in a valid range, an enum value, match a RegEx expression, etc.

You do this by adding validation attributes from the System.ComponentModel.DataAnnotations namespace to the properties in your options class.

 1using System.ComponentModel.DataAnnotations;
 2
 3public class StorageOptions
 4{
 5	public static string SectionName = "Storage";
 6
 7	[Required]
 8	[MinLength(3)]
 9	[MaxLength(32)]
10	public string StorageAccountName { get; init; }
11
12	[Required]
13	public string BlobContainer { get; init; }
14
15	[Range(1, 5)]
16	public int Retries { get; init; } = 3;
17}

With these in place, we just need to tell the runtime to validate our settings when registering the option at startup:

1builder.Services
2    .AddOptions<MyOptionsClass>()
3    .Bind(config.GetSection(StorageOptions.SectionName))
4    .ValidateDataAnnotations();

If there is a validation issue with any of your settings, an OptionsValidationException exception will be raised that you can handle. There is a small gotcha here though… validation will only occur when you resolve the Option into it’s underlying type using the .Value property.

What if you want to validate your settings earlier than that?

Validating on startup

Have no fear, there is a simple way to trigger validation during application startup so that you can be sure your application is configured correctly before getting to far along.

1builder.Services
2    .AddOptions<MyOptionsClass>()
3    .Bind(config.GetSection(StorageOptions.SectionName))
4    .ValidateDataAnnotations()
5    .ValidateOnStart();

Just the beginning

There is a lot more than meets the eye around the Options pattern in .NET. In addition to IOptions<T>, there is also IOptionsSnapshot<T> (for Scoped or Transient scenarios) and IOptionsMonitor<T> that allows for dynamic updates to settings and selective option invalidation.

Hope that helps!