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!