Reloading options at runtime – Options, Settings, and Configuration

A fascinating aspect of the ASP.NET Core options is that the system reloads the value of the options when someone updates a configuration file like appsettings.json. To try it out, you can:

  1. Run the program.
  2. Query an endpoint using the request available in the CommonScenarios.http file.
  3. Change the value of that option in the appsettings.json file and save the file.
  4. Query the same endpoint again, and you should see the updated value.

This is an out-of-the-box feature. However, the system rebuilds the options instance, which does not update the references on the previous instance. The good news is that we can hook into the system and react to the changes.For most scenarios, we don’t need to manually check for change since the value of the CurrentValue property gets updated. However, if you directly reference that value, this mechanism can be useful.In this scenario, we have a notification service that sends emails. The SMTP client’s configurations are settings. In this case, we only have the SenderEmailAddress since sending actual emails is unnecessary. We are logging the notification in the console instead, allowing us to see the configuration changes appear live.Let’s start with the EmailOptions class:

namespace CommonScenarios.Reload;
public class EmailOptions
{
    public string?
SenderEmailAddress { get; set; }
}

Next, we have the NotificationService class itself. Let’s start with its first iteration:

namespace CommonScenarios.Reload;
public class NotificationService
{
    private EmailOptions _emailOptions;
    private readonly ILogger _logger;
    public NotificationService(IOptionsMonitor<EmailOptions> emailOptionsMonitor, ILogger<NotificationService> logger)
    {
        _logger = logger ??
throw new ArgumentNullException(nameof(logger));
        ArgumentNullException.ThrowIfNull(emailOptionsMonitor);
        _emailOptions = emailOptionsMonitor.CurrentValue;
    }
    public Task NotifyAsync(string to)
    {
        _logger.LogInformation(
            “Notification sent by ‘{SenderEmailAddress}’ to ‘{to}’.”,
            _emailOptions.SenderEmailAddress,
            to
        );
        return Task.CompletedTask;
    }
}

In the preceding code, the class holds a reference on the EmailOptions class upon creation (highlighted lines). The NotifyAsync method writes an information message in the console and then returns.

We explore logging in the next chapter.

Because the NotificationService class has a singleton lifetime and references the options class itself, if we change the configuration, the value will not update since the system recreates a new instance with the updated configuration. Here’s the service registration method:

public static WebApplicationBuilder AddNotificationService(
    this WebApplicationBuilder builder)
{
    builder.Services.Configure<EmailOptions>(builder.Configuration
        .GetSection(nameof(EmailOptions)));
    builder.Services.AddSingleton<NotificationService>();
    return builder;
}

How to fix this? In this case, we could fix the issue by referencing the IOptionsMonitor interface instead of its CurrentValue property. However, if you face a scenario where it’s impossible, we can tap into the OnChange method of the IOptionsMonitor interface. In the constructor, we could add the following code:

emailOptionsMonitor.OnChange((options) =>_emailOptions = options);

With that code, when the appsettings.json file changes, the code updates the _emailOptions field. As easy as this, we reactivated the reloading feature.

One more thing, the OnChange method returns an IDisposable we can dispose of to stop listening for changes. I implemented two additional methods in the source code: StartListeningForChanges and StopListeningForChanges, and three endpoints, one to send notifications, one to stop listening for changes, and one to start listening for changes again.

Now that we know how to use the options, let’s explore additional ways to configure them.

Leave a Reply

Your email address will not be published. Required fields are marked *



         


          Terms of Use | Accessibility Privacy