In last article, we had a look at some methods provided by .NET to read the configuration settings. In this article, let’s try to get started on the options pattern, a pattern for reading configuration settings.
Background
Most of those methods, mentioned in the previous blog post, are pretty low level methods
, which provides the settings in very basic form, key-value pair collection.
There is the Bind method, which can be used to load the settings into an object, the method would try to find the properties from the C# object which match with configuration setting’s name to fill the object.
Instead of just having a basic key-value pair collection, it is always wiser to have a set of related settings, bundled together in a strongly typed object.
Strong typing will always ensure that the configuration settings have intended data types.
Keeping related settings together will make sure that the code satisfy two criteria important for any design Encapsulation and Separation of concerns.
It seems that the Bind method solves the separation of concerns issue by having interrelated settings together in one object. But let’s say you want to validate the configuration settings, then what are options for you ?
Or if you want to read configurations at one place and also want to make sure that the objects are updated after configuration is changed when application is running, then what are the choices ?
There the Options pattern comes to the rescue.
Options Pattern
Options pattern is the recommended pattern and it helps to satisfy the two software engineering principals discussed before Encapsulation and Separation of concerns.
What is it?
Options pattern suggests two things:
- there should be a class, Options class, which is not abstract with public parameter-less constructor
- which has public read-write properties
The interface, that can be used for options pattern, IOptions<T> , which needs a type parameter, which should satisfy the Option pattern conditions stated above
Using Bind
Below are the high level steps for implementing Options pattern:
- Create a type (or set of types) which can hold the set of related settings. The class should have public read-write properties from the configurations.
- Then use ConfigurationBinder.Bind to bind the object to configuration section as shown in the below code.
- The object can then be registered in the dependency injection container so that it can be injected into any service.
In this approach, the object is populated as soon as we call the Bind method.
Using Get
The options pattern implemented above using ConfigurationBinder.Bind needs the object already created to populate the object. There is another method ConfigurationBinder.Get<T> , which does not need already created object. It will create the object, populate it and return the object, as shown in below code.
In this approach, the object is populated as soon as we call the Get method.
Configure Dependency Injection
Instead of using Get
or Bind
methods, we can just use GetSection
to get the IConfiguration instance for the subsection from the configuration. And then use Configure method to register the service and convert the IConfiguration into strongly typed object, as shown in below code.
If you use Configure method to register the dependency, and then if you try to inject the class MailFeature
directly in any other service, then you would get an exception as shown below.
You always need to use one of three options interfaces viz., IOptions
, IOptionsSnapshot
, IOptionsMonitor
. Below code uses IOptions
.
Three Interfaces !
There are total three interfaces, which can be used for implementing Options pattern.
We already have seen how to use IOptions interface. The remaining two interfaces can also be consumed in the similar manner.
Then the obvious question may be, why three interfaces ? Let’s have a look at how they are different from one another.
IOptions<T>
This interface is discussed earlier in the post. The type parameter should be class.
This is registered as a Singleton service in the dependency injection container, and hence can be injected in any service lifetime.
The type parameter does not need to be registered in DI container, you can just register IOptions
. The evaluation of type parameter T happens when the property IOptions<TOptions>.Value is accessed.
This interface does not support reloading of configurations after app has started. It also does not support the Named options.
IOptionsSnapshot<T>
This can be used similar to IOptions<T>
code examples shown above. There are some differences though.
This interface is registered as Scoped and hence it can be injected in Scoped or Transient services, but it cannot be used with Singleton services lifetime.
It is useful to use this interface when the options need to be recomputed on every single injection. It supports the Named options.
IOptionsMonitor<T>
This also can be used similar to the code example shown in above section. This interface is registered as Singleton and hence it can be injected in any service lifetime.
This class supports reloading the changed configurations after app has started. It also supports change notifications. It supports the Named options.
In next article, I will try to focus on IOptionsMonitor and how to use it in the application to reload the configurations.
Have you used these interfaces already in some applications? How was your experience? Let me know your thoughts.
Pingback: IOptionsMonitor Demo – Reload Configurations In .NET Applications – The Code Blogger