Home .NET IOptionsand friends

IOptionsand friends

by admin

During development there’s often a need to put parameters into configuration files. Keeping all sorts of configuration constants in your code is a sign of bad manners. One of the options for storing settings – using configuration files. .Net Core out of the box knows how to work with formats like: json, ini, xml, etc. It is also possible to write your own configuration providers. (By the way, the service IConfiguration and IConfigurationProvider – to access the configurations of a certain format and to write their providers)

IOptionsand friends

But the main difficulty, in my opinion, is that we have as many as 3 different interfaces to work with configurations. And it’s not always clear what you need them for and when you need to use which one.

MSDN has article , which should cover all the issues. But, as always, it’s not all that simple.

IOptions

Does not support:
Reading of configuration data after the app has started.
Named options
Is registered as a Singleton and can be injected into any service lifetime.

Basically, the description makes it pretty clear: Loads the data from the configuration file when the application starts, does not pull up any changes.

But I have a big complaint with Microsoft in terms of naming. In my opinion, if a person is not familiar enough with the technology, or sees it for the first time at all, then this interface is the first thing that comes to mind to use. And then, probably far from immediately, the person will find out that something doesn’t work the way he intended.

I’m used to quite a lot of the configurations in my projects being able to change quite often. And personally, I’d like an interface that literally screams to be used for configuration to behave more obviously. (Though of course I could be wrong, and that’s just me being picky. And most people use configuration files differently)

IOptionsSnapshot

Is useful in scenarios where options should be recomputed on every request
Is registered as Scoped and therefore cannot be injected into a Singleton service.
Supports named options

Should work for most of the situations I wrote about above. Updates the configuration information with every request. And importantly, doesn’t change it during the request.
This is suitable for many cases, e.g. feature toggle.

MSDN tells us that it cannot be injected into Singletone – actually it can (which is a topic for a separate post), but then it starts behaving like Singletone itself.

IOptionsMonitor

Is used to retrieve options and manage options notifications for TOptions instances.
Is registered as a Singleton and can be injected into any service lifetime.
Supports:
Change notifications
Named options
Reloadable configuration
Selective options invalidation (IOptionsMonitorCache)

This is essentially real-time access to your configurations. This is where you have to be careful. And if you read the configuration multiple times in the course of any query, you should be prepared that it may change.

IOptionsMonitorCache – interface for building a normal cache based on IOptionsMonitor.

Praktika

All tests were performed in the following environment

sw_versProductName: Mac OS XProductVersion: 10.15.5BuildVersion: 19F101dotnet --version3.1.301

We’ve looked at what the documentation tells us. Now let’s see how it works.

I’ll leave a link to code , if anyone wants to read more.

As an example, a simple Web API

public class Program{public static void Main(string[] args){CreateHostBuilder(args).Build().Run();}public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseKestrel();webBuilder.UseStartup<Startup> ();webBuilder.UseUrls("http://*:5010/");}).UseDefaultServiceProvider(options => options.ValidateScopes = false);}

The client who will access it

static async Task Main(string[] args){using var client = new HttpClient();var prevResponse = String.Empty;while (true){var response = await client.GetStringAsync("http://localhost:5010/settings");if (response != prevResponse) // write to the console only if the settings have changed{Console.WriteLine(response);prevResponse = response;}}}

In the Web API we create 3 services that take all 3 configuration options into constructor and return the current value.

private readonly IOptions<TestGroupSettings> _testOptions;private readonly IOptionsSnapshot<TestGroupSettings> _testOptionsSnapshot;private readonly IOptionsMonitor<TestGroupSettings> _testOptionsMonitor;public ScopedService(IOptions<TestGroupSettings> testOptions, IOptionsSnapshot<TestGroupSettings> testOptionsSnapshot, IOptionsMonitor<TestGroupSettings> testOptionsMonitor){_testOptions = testOptions;_testOptionsSnapshot = testOptionsSnapshot;_testOptionsMonitor = testOptionsMonitor;}

The services will be 3 scopes: Singletone, Scoped, and Transient.

public void ConfigureServices(IServiceCollection services){services.Configure<TestGroupSettings> (Configuration.GetSection("TestGroup"));services.AddSingleton<ISingletonService, SingletonService> ();services.AddScoped<IScopedService, ScopedService> ();services.AddTransient<ITransientService, TransientService> ();services.AddControllers();}

While our Web Api is running, change the value of TestGroup.Test in the appsettings.json file

We have the following picture :
Immediately after start

SingletonService IOptions value: 0SingletonService IOptionsSnapshot value: 0SingletonService IOptionsMonitor value: 0ScopedService IOptions value: 0ScopedService IOptionsSnapshot value: 0ScopedService IOptionsMonitor value: 0TransientService IOptions value: 0TransientService IOptionsSnapshot value: 0TransientService IOptionsMonitor value: 0

Change our setting and we get an interesting picture
Immediately after changing

SingletonService IOptions value: 0SingletonService IOptionsSnapshot value: 0 // has not changedSingletonService IOptionsMonitor value: 0 // has not changedScopedService IOptions value: 0ScopedService IOptionsSnapshot value: // became emptyScopedService IOptionsMonitor value: 0 // has not changedTransientService IOptions value: 0TransientService IOptionsSnapshot value: // became emptyTransientService IOptionsMonitor value: 0 // has not changed

The following output to the console (the config has not changed any more)

SingletonService IOptions value: 0SingletonService IOptionsSnapshot value: 0 // has not changedSingletonService IOptionsMonitor value: 0 // has not changedScopedService IOptions value: 0ScopedService IOptionsSnapshot value: changed setting // changedScopedService IOptionsMonitor value: 0 // has not changedTransientService IOptions value: 0TransientService IOptionsSnapshot value: changed setting // changedTransientService IOptionsMonitor value: 0 // has not changed

Last output (configure is also unchanged)

SingletonService IOptions value: 0SingletonService IOptionsSnapshot value: 0 // has not changedSingletonService IOptionsMonitor value: changed setting // changedScopedService IOptions value: 0ScopedService IOptionsSnapshot value: changed setting // changedScopedService IOptionsMonitor value: changed setting // changedTransientService IOptions value: 0TransientService IOptionsSnapshot value: changed setting // changedTransientService IOptionsMonitor value: changed setting // changed

What do we have in the end? What we have is that IOptionsMonitor is not as fast as the documentation tells us. As you can see, IOptionsSnapshot can return an empty value. But, it works faster than IOptionsMonitor.

So far it is not really clear where this empty value comes from. And the most interesting thing is that this behavior appears not always. In my example, IOptionsMonitor and IOptionsSnapshot work at the same time.

Conclusions

If you need to pass configurations that will never change over the lifetime of your application, you use IOptions.

If your configurations are going to change, it depends. If it’s important to you that your settings are relevant at the time of your query, IOptionsSnapshot is your choice (but not for Singletone, it will never change value). But it is worth considering its oddities, although you are unlikely to encounter them.

If you want the most up-to-date values (or almost), use IOptionsMonitor.

I would be glad if you run the example on your own, and tell me if this behavior repeats itself or not. Maybe we have a bug on macOS, or maybe it’s by design.

I will continue to deal with this topic, but in the meantime have issue , maybe they will clarify this behavior there.

You may also like