Options Pattern In .Net – Binding And Validating Configurations

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.

About the author

Yasin Sunguray

Add comment

Categories

Yasin Sunguray

Hi there,

I write about mostly backend development, you can browse posts about C++, Laravel, .NET, C#, SQL, Vue, React and a few other topics.