Change Notifications and Named Options

using Options pattern in .NET Core

Mohammed Moiyadi
.Net Programming
5 min readMay 21, 2021

--

In this article, I am going to go further deep into additional features provided by IOptions on .NET Core. We are going to build upon the same example as discussed in my previous article on IOptions Create Strongly Typed Configurations in .NET Core. I recommend going through the article to understand the basics of the Options pattern in .NET Core.

Quick Recap of the application: We have a .NET 5 Web API project with an API to generate a report and send that report to some recipients with a subject that we can customize. So we have a ReportController with a post API for the same. We have a couple of services, namely ReportService and EmailService to generate the report and send the report as an email respectively. The EmailService is registered as Singleton scoped dependency and it reads the configuration parameter for Subject and Recipients via IOptionsMonitor.

We will go back to having EmailOptions as the private field in EmailService rather than IOptionsMonitor<EmailOptions>. Since we are reading CurrentValue in the constructor and EmailService is a singleton service, this will give rise to the problem where the service will not read the latest value from the configuration while the application is still running. And we will fix that now using the OnChange listener provided by IOptionsMonitor. In the constructor, in addition to setting the EmailOptions, we will also register an OnChange listener for IOptionsMonitor and in that listener, we will reset our field to the latest value provided by IOptionsMonitor.

EmailOptions in EmailService
Register OnChange in EmailService

Now let us start the application and see what happens when we change configuration values. As soon as I change the configuration values and save the file, the breakpoint which I had set on the OnChange listener got hit.

Email Options modified in appsettings.json
OnChange in EmailService
Application run with Email Options modified

And this is exactly what we wanted. Now, whenever EmailService’s Send method is called, we are sure to read the latest values from the configuration, the reason being that whenever EmailOptions configuration changes, we captured the latest value in the OnChange listener. Note that the OnChange listener is only available for IOptionsMonitor and not IOptionsSnapshot and the reason is that IOptionsSnapshot never had this problem in the first place. With IOptionsSnapshot we were already able to read the latest configuration values in our service.

Now let us consider another scenario. In case some exceptions occur in our code, we want to handle it gracefully and also send an email to the administrators to let them know about the exception. We want to use the same EmailService to send the admin email. Let us first write some code to handle the exceptions in our code. We will add UseExceptionHandler middleware in Configure method of the Startup class and handle the exception thrown from subsequent middleware in the pipeline. We can also inject IEmailService in the Configure method since we have already registered it as a dependency in the ConfigureServices method. It is this code where we will retrieve the exception details and send emails to the administrators. Please Note: this is just an example and it is not the standard way how notifications are handled for an application.

UseExceptionHandler in Configure

Let us now implement the SendAdmin method that we have used in Exception Handler in our EmailService. Also, let us add another section called AdminEmail to our appsettings.json file. In the SendAdmin method, we want to read from the AdminEmail section rather than the Email section of the application. Let us see how we can implement this with our existing setup. We will create another class for AdminEmailOptions similar to EmailOptions class and then inject it the same way as EmailOptions into the EmailService.

appsettings.json with AdminEmail section
ConfigureServices in Startup.cs
IEmailService.cs
EmailService.cs

And we are done!!! At this point, I am not very excited about this implementation since there is too much code duplication. And sure there is a better way but first, let us make sure the implementation works. For this, we need to have our application throw some errors so that the Exception Handler comes into action. Let us throw an exception from ReportService and run the application

Throwing an exception from ReportService.cs
Application running with exception thrown

As we can see, the AdminEmailOptions has correctly read the AdminEmail section of the configuration. As I mentioned earlier, there is too much code duplication with this approach. Essentially we are using the same structure of Email Options with different values at different places. We should be able to use the same class for both and that is what we are going to do next. So first of all, let us get rid of the additional AdminEmailOptions class and add the section name for AdminEmail in the existing EmailOptions class

EmailOptions.cs

Since we do not have AdminEmailOptions class anymore, we will remove its configuration in the ConfigureServices method and we will configure EmailOptions twice. One for the Email section and the other for AdminSection. The difference from the earlier version is that now we are passing section name as the first parameter. And this is the crux of the solution. What we have implemented here is called Named Option. We have registered two instances of EmailOptions with distinct names and whenever we want to fetch the Email Options from configuration, we have to specify the name of the configuration.

Register Named Options in Startup for EmailOptions

Now, let us fix our EmailService. We don't need AdminEmailOptions anymore. Instead, we will just change the type of adminEmailOptionsVal field to EmailOptions. In the constructor, we need just one instance of IOptionsMonitor and while setting the two fields we just need to call its Get method and pass the name of the section.

EmailService.cs

And that is all. Now if we run the application, we can see that both the Send method and SendAdmin method are reading the Email parameters from their respective section. Instead of throwing from ReportService, we will throw an exception from EmailService’s Send method just after we have written to console so that we are able to call both Send and SendAdmin methods in the same run.

Exception thrown from Send method of EmailService

And if we run the application now, we can see that both the Send and SendAdmin methods have picked values from their respective configuration section

So with that, we conclude the demonstration of the Options pattern in .NET Core. Options pattern is a very useful feature provided in .NET Core applications and some of the features that we have covered are:

  • Strongly Typed Configurations
  • Reading configuration changes after the application have started
  • Named Options

These features are provided via IOptions, IOptionsSnapshot, and IOptionsMonitor interface and we should use the implementation as per the need of our application.

The source code for this application can be found at mmoiyadi/IOptions.NetCore.

Happy Coding !!!

--

--