- Home
- .NET tutorials
- Do you use IConfiguration, IOptions or IOptionsSnapshot?
Do you use IConfiguration, IOptions or IOptionsSnapshot?
Published: Monday 15 July 2024
When injecting configuration settings from appsettings.json
into an ASP.NET Core app, there are a number of ways you can do it.
We are going to have a look at injecting IConfiguration
, IOptions
and IOptionsSnapshot
to see which one we should use.
Adding configuration values to appsettings
To begin with, we are going to add product configuration values to appsettings.json
.
C# coding challenges
We'll include the currency, whether there is a discount applied and whether we are going to show reviews.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Product": {
"Currency": "USD",
"Discount": 7,
"Reviews": true,
}
}
Using IConfiguration
We can inject the IConfiguration
instance into any class that supports dependency injection. There doesn't need to be any extra configuration settings added in Program.cs
to use this.
Once the IConfiguration
instance has been injected, we can call the GetValue
method from the reference. The GetValue
method requires a generic type, and we can add the type that we are expecting.
For example, if we want to call Discount
from the Product
configuration values, we would add decimal
as the generic type.
Afterwards, we add the configuration key to get the value. If the key has parent configuration keys, they are added first and separated by a colon.
As all the product configuration values have a parent key of Product
, we would add that first followed by a colon, followed by the configuration value that we wanted to store.
So for showing product reviews, this would be Product:Reviews
.
// ConfigurationController.cs
public class ConfigurationController : ControllerBase
{
private readonly IConfiguration _configuration;
public ConfigurationController(
IConfiguration configuration)
{
_configuration = configuration;
}
[HttpGet]
public IActionResult GetProduct()
{
return Ok(new
{
Currency = _configuration.GetValue<string>("Product:Currency"),
Discount = _configuration.GetValue<decimal>("Product:Discount"),
Reviews = _configuration.GetValue<bool>("Product:Reviews"),
});
}
}
When we test this endpoint, it outputs the configuration values. And if we change the configuration values in appsettings.json
and rerun the endpoint, it updates the values without restarting the application.
The problem with this method is that any extra configuration values added to Product
would need to be updated in all places where it's being used.
Bind the configuration values to a class
Another option is that we can bind the configuration values to a class.
We'll create a ProductOptions
class that will contain properties for our configuration values and the type that we are expecting.
// ProductOptions.cs
public class ProductOptions
{
public string Currency { get; init; }
public decimal Discount { get; init; }
public bool Reviews { get; init; }
}
There are two ways we can bind the values from appsettings.json
into this class.
We can either get them from IConfiguration
and provide the class as the generic type. Or we can bind them to an existing instance of ProductOptions
.
// ConfigurationController.cs
public class ConfigurationController : ControllerBase
{
private readonly IConfiguration _configuration;
public ConfigurationController(
IConfiguration configuration)
{
_configuration = configuration;
}
...
[HttpGet("with-configuration-get")]
public IActionResult GetProductWithConfigurationGet()
{
var productOptions = _configuration.GetSection("Product").Get<ProductOptions>();
return Ok(productOptions);
}
[HttpGet("with-configuration-bind")]
public IActionResult GetProductWithConfigurationBind()
{
var productOptions = new ProductOptions();
_configuration.GetSection("Product").Bind(productOptions);
return Ok(productOptions);
}
}
You can inject IOptions if you are using dependency injection
If you are using dependency injection, you can add the options to the IoC container by providing the configuration key.
Here, we are adding the ProductOptions
and binding them to the Product
section from the configuration.
// Program.cs
builder.Services.AddOptions<ProductOptions>()
.Bind(builder.Configuration.GetSection("Product"));
Then you can inject them by calling IOptions
, adding the class with the configuration value properties as the generic type.
// ConfigurationController.cs
[Route("api/[controller]")]
[ApiController]
public class ConfigurationController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly ProductOptions _productOptionsValue;
public ConfigurationController(
IConfiguration configuration,
IOptions<ProductOptions> productOptions
{
_configuration = configuration;
_productOptionsValue = productOptions.Value;
}
...
[HttpGet("with-ioptions")]
public IActionResult GetProductWithIOptions()
{
return Ok(_productOptionsValue);
}
}
However, when you try to change the configuration values when using IOptions
, you have to restart the application before the configuration values change.
Is there another way we can do it?
Using IOptionsSnapshot will update configuration values
Instead of using IOptions
, we can use IOptionsSnapshot
. IOptionsSnapshot
works in a similar way to IOptions
, but it updates the configuration values without restarting the application.
The reason being is that IOptions
is set up as a singleton lifetime scope in dependency injection, whereas IOptionsSnapshot
is set up as scoped.
// ConfigurationController.cs
public class ConfigurationController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly ProductOptions _productOptionsValue;
private readonly ProductOptions _productOptionsSnapshotValue;
public ConfigurationController(
IConfiguration configuration,
IOptions<ProductOptions> productOptions,
IOptionsSnapshot<ProductOptions> productOptionsSnapshot)
{
_configuration = configuration;
_productOptionsValue = productOptions.Value;
_productOptionsSnapshotValue = productOptionsSnapshot.Value;
}
...
[HttpGet("with-ioptions-snapshot")]
public IActionResult GetProductWithIOptionsSnapshot()
{
return Ok(_productOptionsSnapshotValue);
}
}
Adding a nested configuration class
IOptions
and IOptionsSnapshot
has support for nested configuration values.
To demonstrate this, we've added a Category
section to appsettings.json
.
"Product": {
"Currency": "USD",
"Discount": 19,
"Reviews": true,
"Images": true,
"Category": {
"IncludeImage": true
}
}
Then we need to create a class for the category options and include any properties where we are expecting configuration values.
// CategoryOptions.cs
public class CategoryOptions
{
public bool IncludeImage { get; init; }
}
This class can then be added as a property type in ProductOptions
.
// ProductOptions.cs
public class ProductOptions
{
public string Currency { get; init; }
public decimal Discount { get; init; }
public bool Reviews { get; init; }
// Will bind the configuration from Product:Category in appsettings.json
public CategoryOptions Category { get; init; }
}
You can watch our video where we go through IConfiguration
, IOptions
and IOptionsSnapshot
and test each one to see the expected output.
It's best to use IOptionsSnapshot
In conclusion, it's best to use IOptionsSnapshot
if you're using dependency injection.
It has the benefits of binding configuration values to a class, changing configuration values without restarting the application and being able to use nested configuration values.
Although IConfiguration
also allows you to change configuration values without restarting the application, there is a lot more code and potentially repeating code to reach the same result.