Reading Configuration Values in Dotnet Core Using Options Pattern
Normally every project will have some values to be read from config files like user name, connection strings, environment specific details etc.
Dotnet Framework
In dotnet framework projects, this can be easily done by using Configuration Manager. This is available when we add System.Configuration assembly reference.
Let us look at an example of a app config file like below
Storing user name and password in app config is not a best practise. For sake of simplicity , let us keep it here. In dotnet framework , we can read above config values with below
There are different approaches to read the config file. Let us look at couple of them.
IConfiguration
One of the approach to consume this is using IConfiguration Interface. We can inject this to the class constructor where we need to consume these config values.
1234567891011121314151617181920
//This is not entire class file. It just show the main areas where we need to make changes.//Using statementusingMicrosoft.Extensions.Configuration;// Make sure to create a private variable and inject IConfiguration to constructorIConfiguration_configuration;publicdemo(IConfigurationconfiguration){_configuration=configuration;}//Usage is as below. Use GetValue method and specify the type. //Also we need to give entire hierarchy staring with section value to the keyname separated by :varUrl=_configuration.GetValue<string>("SauceDemoDetails:Url");varUserName=_configuration.GetValue<string>("SauceDemoDetails:UserName");varPassword=_configuration.GetValue<string>("SauceDemoDetails:Password");
However injecting IConfiguration is not a good practise since we are not sure what configuration is this class now depend on. Also class should know the entire hierarchy of configuration making it tightly coupled.
IOptions
Another way to access these config values is by using Options Pattern . Documentation of that can be found here. The options pattern uses classes to provide strongly typed access to groups of related settings. It also provides way to validate details.
In this pattern, we need to create an options class corresponding to the config value
According to documentation, options class should be non abstract class with public parameterless constructor. All public get - set properties of the type are bound and fields are not bound. Hence in above class, Url, UserName, Password are bound to config values and SauceDemoDetails are not bound.
Let us see how we can bind the configuration file to above created class. In startup.cs file, do below
123456789101112131415161718192021222324
// Use configuration builder to load the appsettings.json filepublicStartup(IHostingEnvironmentenv){varbuilder=newConfigurationBuilder().SetBasePath(env.ContentRootPath).AddJsonFile("appsettings.json",optional:true,reloadOnChange:true).AddJsonFile($"appsettings.{env.EnvironmentName}.json",optional:true);if(env.IsDevelopment()){builder.AddUserSecrets();}builder.AddEnvironmentVariables();Configuration=builder.Build();}// Configure Services to bind//Need to give entire hierarchy separated by : . In below example, I use the constant string from the class to specify so that it doesn't need to be hard coded here.publicvoidConfigureServices(IServiceCollectionservices){services.Configure<SauceDemoDetailsOptions>(Configuration.GetSection(SauceDemoDetailsOptions.SauceDemoDetails));}
Inorder to use this, we need to inject IOptions to the consuming class.
123456789101112131415161718
//This is not entire class file. It just show the main areas where we need to make changes.//Using statementusingMicrosoft.Extensions.Options;// Make sure to create a private variable and inject IOptions to constructor. IOptions expose Value property which contains the details of object.privateSauceDemoDetailsOptions_sauceDemoDetailsOptions;publicdemo(IOptions<SauceDemoDetailsOptions>sauceDemoDetailsOptions){_sauceDemoDetailsOptions=sauceDemoDetailsOptions.Value;}//Usage is as below. :varUrl=_sauceDemoDetailsOptions.Url;varUserName=_sauceDemoDetailsOptions.UserName;varPassword=_sauceDemoDetailsOptions.Password;
One important point to remember is Ioptions interface doesn’t support reading configuration data after app has started. Hence any changes to appsettings.json after the app has started will not be effective till next restart. If we need to recompute the values every-time, then we should use IOptionsSnapshot interface. This is a scoped interface which cannot be injected to Singleton service. The usage of this is same as IOptions interface. We also need to make sure that configuration source also supports reload on change.
Validations
Let us look into how to implement validations into this. In above examples, if there are any mistakes like typo ,missing fields etc, then those fields will not be bound. However it will not error out there and will continue execution , till it throws an exception where it require these missing fields. We can add validations from DataAnnotations library to identify these earlier.
DataAnnotations provide various validator attributes like Required, Range, RegularExpression, String Length etc.