In this post, we are going to implement Options Pattern with .Net 6.
What is Options Pattern?
The Options Pattern allows us to access strongly typed application settings by using classes and reading from the appsettings.json file. Benefits are testing gets easier(easy to mock), live reloading is enabled, validation is possible.
It also helps ensure the following principles.
Encapsulation, only the needed parts of the configuration are used by dependent classes, Separation Of Concerns(SOC), different parts of the configuration are not dependent to one another, decreases coupling.
BINDING
Imagine we have the following structure in appsettings.json file.
"StreamServerSettings": {
"Url": "stream.myapp.com",
"Port": 9092
}
To be able to bind this section, we need to create a class, named as StreamServiceConfiguration. With a concrete typed class and appropriate type properties we can enforce validation rules on them easily, later.
Create the StreamServiceConfiguration class, add properties. I’ve added an extra SectionName constant to help us easily change configuration section name if needed.
public class StreamServiceConfiguration
{
/* configuration section name */
public const string SectionName = "StreamServerSettings";
/* service url */
public string Url { get; set; }
/* service port */
public int Port { get; set; }
}
Next, we need to bind this class. Add the following code section to your services configuration.
builder.Services.AddOptions<StreamServiceConfiguration>().Bind(builder.Configuration.GetSection(StreamServiceConfiguration.SectionName));
Now the binding section is completed, let’s add some validation to our configuration class.
Validating
In order to validate StreamServiceConfiguration class attributes, we need DataAnnotations namespace. Add a couple of rules to properties as you need. I’ve just added Regex and Range rules. We don’t need to add any rule to SectionName constant, it won’t be validated.
public class StreamServiceConfiguration
{
/* configuration section name */
public const string SectionName = "StreamServerSettings";
/* service url */
[RegularExpression("^https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)$")]
public string Url { get; set; } = string.Empty;
/* service port */
[Range(1025, 65535)]
public int Port { get; set; }
}
In order to validate StreamServiceConfiguration class, we need to modify binding code block as follows.
builder.Services.AddOptions<StreamServiceConfiguration>().Bind(builder.Configuration.GetSection(StreamServiceConfiguration.SectionName)).ValidateDataAnnotations().ValidateOnStart();
We’ve chained two extensions methods to our existing code, simply explaining;
ValidateDataAnnotations(), ensures data annotation validations are applied to the properties.
ValidateOnStart(), ensures that validation occurs at application start time rather than runtime.
USING
Now we can inject StreamServiceConfiguration class anywhere in our application and access bound configuration values. Of course, we need to use one of the following interfaces that Microsoft provides us,
IOptions<T>
- Registered as singleton
- Doesn’t support reloading after the application start
- Binds configuration values only once at registration and returns same values every time
IOptionsSnapshot<T>
- Registered as scoped
- Supports reloading
- Reloads configuration values per request
- Might be heavy on performance wise
IOptionsMonitor<T>
- Registered as singleton
- Supports reloading
- Caches configuration values and reloads immediately
Sample use with IOptionsMonitor (don’t take it seriously 😀 );
[Route("api/robots")]
[ApiController]
public class RobotController : ControllerBase
{
private readonly IOptionsMonitor<StreamServiceConfiguration> _streamServiceSettingsDelegate;
public RobotController(IOptionsMonitor<StreamServiceConfiguration> streamServiceSettingsDelegate)
{
_streamServiceSettingsDelegate = streamServiceSettingsDelegate;
}
[HttpGet]
[ProducesResponseType(200)]
public async Task<IActionResult>GetStreamServerConfigurationDetails(CancellationToken cancellationToken)
{
string url = _streamServiceSettingsDelegate.CurrentValue.Url;
int port = _streamServiceSettingsDelegate.CurrentValue.Port;
return Ok();
}
}
Till the next time.