Options Pattern in ASP.NET Core

Options Pattern in ASP.NET Core

Last Updated on August 21, 2025 by Aram

Since the beginning of ASP.NET Core, there has been a great addition in the main features, one of which is the appsettings.json file.

In this tutorial you will learn how to access the appsettings.json file through a uniquely powerful pattern introduced in ASP.NET Core, it is the Options Pattern.

What is Appsettings.json?

Appsettings.json is a simple JSON-formatted file which typically resides within the root folder of your ASP.NET Core Web Application project, it can include anything from connection strings, API keys or secrets (must be saved hashed or transferred to a separate location, like a vault), application-related settings, URLs to different API integrations, logging configurations and many more.

In the previous version of ASP.NET, we used to have the web.config file which included such details alongside other runtime configurations for some dependency libraries.

Now in .NET Core and most recent versions of .NET, this has been replaced with the improved configuration file which is the appsettings.json

In fact, the appsettings.json can be split into multiple files, each one targeting a specific platform, so you can have a file that includes settings targeting development environment which having another file targeting the production environment.

so namely we can have appsettings.development.json and appsettings.json files, and if you have a pre-prod or qa environment, then you can just name it accordingly: appsettings.qa.json.

Once you create the new file, In the solution explorer, you will notice that the development version of the file is actually hidden within the appsettings.json file, which is a logical grouping that VS will show it for you.

What is the Options Pattern?

One of the recommended and heavily used patterns to load the appsettings.json with its different environment variations, is through the options pattern.

Options pattern is a design pattern that is mainly used in ASP.NET Core to bind and group configurations that are defined in appsettings.json to strongly-typed classes in C#

This is rather helpful when you want to use your configurations in different parts of your ASP.NET Core application.

Later in this article, you will see how you can use the IOptions to bind the ApiSettings section of the appsettings.json with the ApiSettingsModel and inject it across the different components of the ASP.NET Core Web API project, and these include the Controller, Middleware, Services.

In fact, with the great dependency injection feature of ASP.NET Core, you can inject the IOptions virtually anywhere in your application.

Let’s start this tutorial to use Options Pattern to access appsettings.json

Create a new project using Visual Studio 2022, choose ASP.NET Core Web API.

Let’s choose .NET 9 and then press Create.

Once VS 2022 finishes project setup, in the solution explorer you will notice an expandable view of the file appsettings.json, as mentioned previously.

It will expand to show the appsettings.development.json

Opening the 2 files will show the below JSON-formatted content:

You will notice that each one of the files has almost matching structure, with values that might be different between the environments and some file might have an extra key or object.

We will build a Web API that expose endpoints related to hashing, we will be mainly implementing 2 types of hashing functions

PbKFD2 which stands for Password-based Key Derivation Function

and HMAC-256, this function calculates the message authentication code using SHA-256 as the hashing function

Now we are ready to start preparing the structure of the tutorial, so let’s get started:

appsettings.json

Open appsettings.json

add the below section:

Then open the file appsettings.development.json and add the below ApiSettings Section:

API Key

You might wonder from where did we bring the values of the ApiKeyHash for both environments.

So, I simple generated a random alphanumeric series of 30 characters from any online generation tool, so I got the below API Keys

atw35lrqs12cqvrwhaeee7366em6ky for production which is represented by the hash +493O7g2eN1Q2KOMa8+2pvT2aX83hcCmfxkdPpGRy/g=

gkgw3vqares09fr2m5dh6lpkwf1b9k for development which is represented by the hash +KhteeZa4ydjFaCQ+QXmYIli5XKEztJTocmbuoE65Eg=

So usually the best practice for API key is to share it with client and never store it anywhere in your code or even settings.

Better to hash it (with a strong hashing algorithm) and save it on settings

For the knowledge, I’ve used the pbkdf2 key-derviation function to find the hash for the API Keys mentioned above.

Now, in order to be able to use the Options pattern in the code, we need to have a model (class) that would bind to the ApiSettings Section within the appsettings.json file

So let’s go and create a new class with name ApiSettingsModel inside a new folder with name Models:

Interfaces

Create an Interfaces Folder.

Create a new Interface with name IHashingService.

We will have our 2 methods signatures defined in the IHashingService Interface

Services

Now let’s create the service that would implement the hashing functions defined in the IHashingService

The most important part of this service class is the usage of IOptions<T> , since IOptions is a singleton instance service, any change after loading the appsettings.json will not be reflected .

Middleware

So we will be using a middleware to provide basic authentication for the APIs.

Side Note: I don’t prefer to pluralize Middleware and name it Middlewares, it doesn’t sound or rhyme right, and I guess it falls under the same category of Software. Here is a long discussion about this topic if anyone wants to read further.

This is mainly intended to showcase that we can easily use any section from the appsettings.json file within any component of our ASP.NET Core application, and the middleware is an example for that.

Create a new class with name ApiKeyMiddleware inside a new folder ‘Middleware’:

Controllers

Let’s define our endpoints of hashing functions.

Also we will be using another setting defined in ApiSettings section here in the controller to be able to enable/disable one of the test controllers.

And that will happen using the IOptions<T>

Here is the final code for the HashingController

launchSettings.json

When working locally, you have the launchSettings.json file that includes the different build profiles and with each profile you have the ASPNETCore_Environment variable.

If you keep the Environment value as empty string, the default appsettings.json file should be loaded, and the same goes with setting the value as Development.

Program.cs

Now open program.cs file and make sure it looks like the below:

So in the builder.Services.Configure<T> we are binding the ApiSettings section of appsettings.json to the ApiSettingsModel

And see how we are using the ApiKeyMiddleware by injecting it into ASP.NET Core Application’s pipeline.

Testing on Postman

I like to use postman for my API testing, it is a nice and powerful tool.

First I will see if the /Test endpoint will work without passing Api Key:

As you can see, the ApiKey Middleware intercepted the call and checked the headers, and founder that the ApiKey header was not passed, so it returned 401 with the same message we included in the middleware class in case the Api Key was not provided.

Now let’s try to provide an Api Key that is invalid:

As you can see, we got the same http response code 401 but different message.

Now let’s test with the correct Api Key, the development one, as provided earlier in this article:

Let’s do another test to see how changing the ASPNETCore_Environment variable would reflect on the output of the above endpoint.

Now let’s stop the application and open launchSetting.json

Navigate to https in the profiles and remove the ASPNETCORE_ENVIRONMENT.

Now run the project again, open postman, and call the Test endpoint

We got unauthorized because the Api Key of development would no longer work on the current setup, since we are using the default appsetting.json file, which is the production one, and the ApiKey of production is different than the one on development.

Now, let’s change the ApiKey to the production one and call the endpoint again:

This time we have got 404 because the flag TestingEndpointEnabled is set to false, and our logic in the api if this flag is set to false then return 404.

I will stop testing here, and I will ask you to continue testing the rest of endpoints as a homework for you.

Homework

Here are some exercises for you to continue the testing, this way you can make sure to implement the code at your side and continue testing.

Run the application on your localhost, and test the below endpoints:

/api/Hashing/PbKdf2

/api/Hashing/HMAC256

These endpoints would perform hashing using password-based Key derivation function and will apply the number of iterations based on the configuration on each of the appsettings files according to the loaded environment, as you’ve seen in the previous test.

3 Types of Options Pattern

There are 3 different types and Interfaces for Options, all 3 of them have very similar functionality of loading the appsettings.json file and binding to a strongly-typed models.

IOptions

This is the most commonly used pattern, the IOptions service is injected as singleton instance, so you can have the same value across all the different parts of your project and during the lifetime of the application session, which can include multiple requests.

Even if you try changing the setting on the json file, the settings will remain the same until the session is terminated, (i.e.) the IIS pool that is hosting the ASP.NET Core application is recycled.

This part was already covered in the above tutorial.

IOptionsSnapshot

This is injected as a scoped service, a scoped service would keep persisting the same values throughout the same HTTP request and across multiple requests for the same injected instance.

Best used when config might change between requests (e.g., tenant-specific configs).

Note that IOptionsSnapshot cannot be injected into a Singleton service, since it is injected as scoped.

So, you can inject the ApiSettingsModel as IOptionsSnapshot by changing it wherever you will try to inject the options there.

IOptionsMonitor

Similar to IOptions, this is injected as a singleton instance, but the difference is that with IOptionsMonitor you can have a live update for any changes related to the appsettings.json file

Using the CurrentValue property will give you the latest update on the configuration at runtime without having to restart your application.

And also if you need to react to configuration changes, like logging it or triggering some action based on it, then you can call the OnChange method and do whatever action you want inside it:

[Route("api/[controller]")]
[ApiController]
public class HashingController : ControllerBase, IDisposable
{
    private readonly IHashingService hashingService;
    private readonly IOptionsMonitor optionsMonitor;
    private readonly IDisposable? onChangeSubscription;

    public HashingController(
        IHashingService hashingService,
        IOptionsMonitor optionsMonitor)
    {
        this.hashingService = hashingService;
        this.optionsMonitor = optionsMonitor;

        // Register OnChange and store the IDisposable
        onChangeSubscription = optionsMonitor.OnChange(updatedOptions =>
        {
            // React to changes if needed
            Console.WriteLine($"TestingEndpointEnabled changed to: {updatedOptions.TestingEndpointEnabled}");
        });
    }

// code removed for brevity

 public void Dispose()
 {
     onChangeSubscription?.Dispose();
     GC.SuppressFinalize(this);
 }

}



This registers a listener (callback event) whenever an option (or setting) is changed.

Also note that this method returns IDisposable, so this should be disposed to stop listening to changes and avoid memory issues. The above example shows how to use this with proper disposing.

Another note is that cannot use primary constructor with a class that implements IDisposable, because primary constructors do not allow you to declare fields directly or implement the Dispose pattern cleanly.

More Exercises

Test the code with the 2 other Options while the application is running, and see how the configuration would be reflected.

Summary

In this tutorial we got introduced to the Options Pattern and how it helps in accessing configurations within the appsettings.json files cleanly and easily.

Options pattern is a great way to access settings with different environments in your application to build robust and flexible Web API

We learned how we can inject the appsettings.json ApiSettings section into your ASP.NET Core project and bind it to a strongly-typed model.

Also we have seen how using the Options Pattern represented by IOptions<T> as one of the options types to inject a singleton instance of your bound ApiSettings section into different parts of your ASP.NET Core application

And that includes injecting them within Controllers, Middleware and Services.

We tested the application using Swagger to see the appsettings.json working in action.

Lastly, we got introduced to the other types of Options Pattern, IOptionsSnapshot and IOptionsMonitor and how to use them the right way.

References

Options pattern in ASP.NET Core

Extensive read about Configurations in .NET

Recent Posts

ASP.NET Core Data Protection API

Unleash the Power of Bulk Extensions with Dapper Plus

Top 10 Middleware in ASP.NET Core Web API

Collaborations

I am always open to discuss any protentional opportunities and collaborations.

Check this page to learn more about how we can benefit each other.

Sponsorships and Collaborations

Bonus

Here is a wonderful masterpiece by the Poet of the Piano - Chopin, played by the brilliant pianist Rousseau

Chopin - Nocturne in E Flat Major (Op. 9 No. 2)

Leave a Reply