blog
Ottorino Bruni  

How to Use the Options Pattern in ASP.NET Core for Strongly Typed Configuration

Why You Need the Options Pattern for Configuration

When building applications in ASP.NET Core, configuration plays a critical role. Almost every project needs to load values from appsettings.json or environment variables, database connection strings, API keys, feature flags, and more.

The most common way to read configuration is by using the IConfiguration interface and accessing values with string keys like:

var value = configuration["MySection:MyValue"];

While this works, it comes with problems:

  • Keys are just strings, so typos cause runtime errors.
  • No compile-time safety or IntelliSense support.
  • Hard to maintain as the number of settings grows.

The Options Pattern solves these issues by mapping configuration sections into strongly-typed classes. Instead of relying on raw strings, you work with C# objects that give you type safety, validation, and a cleaner design.

In this article, we’ll explore how to use the Options Pattern in ASP.NET Core with a Minimal API example.

The Options Pattern

The Options Pattern in ASP.NET Core uses classes to provide strongly typed access to groups of related settings. Instead of scattering configuration values throughout your code, you define dedicated classes that represent a logical group of settings.

When configuration settings are isolated into separate classes, the app follows two important software engineering principles:

  • Encapsulation
    Classes that depend on configuration settings only rely on the specific settings they need, not the entire configuration source.
  • Separation of Concerns
    Settings for different areas of the application are independent and not coupled to each other. For example, database settings can live in one class, while email or API settings live in others.

Another advantage is that Options provide a mechanism to validate configuration data at startup. This prevents your app from running with invalid or missing settings.

For more details, see the official Microsoft documentation on Options validation.

How to Use the Options Pattern

Using the Options Pattern in ASP.NET Core follows a simple process:

  1. Create a class
    Define a C# class that represents a group of related settings (for example ApiSettings with properties like BaseUrl, MaxRetries, and TimeoutSeconds).
  2. Define configuration in appsettings.json
    Add a section in appsettings.json that matches the class name or the chosen section name.
  3. Bind the configuration to the class
    In Program.cs, register the class with the dependency injection container using Configure<T> and bind it to the corresponding section in the configuration.
  4. Inject the settings where needed
    Access the configuration values in your code by injecting IOptions<T>, IOptionsSnapshot<T>, or IOptionsMonitor<T>. Each of these interfaces provides a slightly different behavior for accessing configuration data.

This approach ensures that configuration data is strongly typed, validated, and easy to maintain across different parts of the application.

Example: Custom Middleware in ASP.NET Core Minimal API

Before we start with the code, it’s important to mention a design principle that also applies to middleware: the Single Responsibility Principle (SRP).

Each middleware should ideally take care of one responsibility only.

  • If you try to combine multiple concerns (for example logging, correlation IDs, and exception handling) into a single middleware, the code becomes harder to test and maintain.
  • By keeping them separate, each middleware is focused and reusable in different projects.

This is also the reason why the ASP.NET Core pipeline is so flexible: you can compose as many middleware as you want, each doing a specific job.

In the following example we’ll create two distinct middleware:

  1. One for logging slow requests.
  2. Another for adding correlation IDs.

Both are small, independent, and easy to understand — and when combined in the pipeline, they provide powerful observability features.

Example: Using the Options Pattern in ASP.NET Core Minimal API

Disclaimer: This example is purely for educational purposes. There are better ways to write code and applications that can optimize this example. Use this as a starting point for learning, but always strive to follow best practices and improve your implementation.

Prerequisites

Before starting, make sure you have the following installed:

  • .NET SDK: Download and install the .NET SDK if you haven’t already.
  • Visual Studio Code (VSCode): Install Visual Studio Code for a lightweight code editor.
  • C# Extension for VSCode: Install the C# extension for VSCode to enable C# support.

Step 1 – Create a Minimal API Project

Open your terminal and run:

dotnet new web -n OptionsPatternDemo
cd OptionsPatternDemo

This will generate a minimal ASP.NET Core project with a basic Program.cs file.

Step 2 – Add Configuration to appsettings.json

Open appsettings.json and add a new section for ApiSettings:

{
  "ApiSettings": {
    "BaseUrl": "https://example.com/api",
    "MaxRetries": 3,
    "TimeoutSeconds": 30
  }
}
How to Use the Options Pattern in ASP.NET Core for Strongly Typed Configuration – appsettings

Step 3 – Create the Settings Class

Add a new file ApiSettings.cs with the following content:

public class ApiSettings
{
    public string BaseUrl { get; set; } = string.Empty;
    public int MaxRetries { get; set; }
    public int TimeoutSeconds { get; set; }
}

This class represents the configuration section and gives you strongly typed access to the settings.

How to Use the Options Pattern in ASP.NET Core for Strongly Typed Configuration – ApiSettings

Step 4 – Register the Options in Program.cs

In Program.cs, bind the configuration to the ApiSettings class:

var builder = WebApplication.CreateBuilder(args);

// Register ApiSettings in the DI container
builder.Services.Configure<ApiSettings>(
    builder.Configuration.GetSection("ApiSettings"));

var app = builder.Build();

Step 5 – Use the Options in a Minimal API Endpoint

Now inject IOptions<ApiSettings> into an endpoint and return the values:

app.MapGet("/settings", (IOptions<ApiSettings> options) =>
{
    var settings = options.Value;
    return Results.Ok(new
    {
        settings.BaseUrl,
        settings.MaxRetries,
        settings.TimeoutSeconds
    });
});

app.Run();
How to Use the Options Pattern in ASP.NET Core for Strongly Typed Configuration – Settings Api

Run the project and open the Settings Api in your browser. You should see the settings returned as JSON.

How to Use the Options Pattern in ASP.NET Core for Strongly Typed Configuration – Run Program

Validating Options

One of the key benefits of the Options Pattern is the ability to validate configuration at startup. This prevents your application from running with invalid or missing values.

To enable validation, first install the Microsoft.Extensions.Options.DataAnnotations package:

dotnet add package Microsoft.Extensions.Options.DataAnnotations

Updating the Settings Class

Use data annotation attributes to enforce validation rules:

using System.ComponentModel.DataAnnotations;

public class ApiSettings
{
    [Required]
    [Url]
    public string BaseUrl { get; set; } = string.Empty;

    [Range(1, 10)]
    public int MaxRetries { get; set; }

    [Range(1, 120)]
    public int TimeoutSeconds { get; set; }
}

Registering with Validation

In Program.cs, configure ApiSettings with validation:

builder.Services
    .AddOptions<ApiSettings>()
    .Bind(builder.Configuration.GetSection("ApiSettings"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

What Happens Now?

  • If BaseUrl is missing or not a valid URL, the app fails to start.
  • If MaxRetries is outside the range 1–10, startup fails.
  • If TimeoutSeconds is outside the range 1–120, startup fails.

This way, configuration errors are caught early instead of surfacing at runtime.

Putting it all together, your Program.cs should look like this:

using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOptions<ApiSettings>()
    .Bind(builder.Configuration.GetSection("ApiSettings"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/settings", (IOptions<ApiSettings> options) =>
{
    var settings = options.Value;
    return Results.Ok(new
    {
        settings.BaseUrl,
        settings.MaxRetries,
        settings.TimeoutSeconds
    });
});

app.Run();

public class ApiSettings
{
    [Required]
    [Url]
    public string BaseUrl { get; set; } = string.Empty;

    [Range(1, 10)]
    public int MaxRetries { get; set; }

    [Range(1, 120)]
    public int TimeoutSeconds { get; set; }
}

Conclusion

The Options Pattern is one of those features in ASP.NET Core that looks simple at first glance, but brings significant long-term benefits. By grouping related configuration into strongly typed classes, you make your applications more maintainable, testable, and less error-prone.

This pattern helps enforce important software engineering principles such as Encapsulation and Separation of Concerns, reducing the risk of having configuration code scattered across the project. With support for dependency injection, your services only depend on the settings they actually need.

Another key advantage is validation. Instead of letting your application run with missing or invalid configuration values, you can validate them at startup. This provides faster feedback during development and safer deployments in production.

And remember: the Options Pattern is not limited to web applications. You can apply the same approach in console apps, worker services, or any other .NET application where structured configuration is required.

Key takeaways:

  • Strongly typed configuration, safer and easier to maintain compared to string-based access.
  • Encapsulation & Separation of Concerns, each part of your app only depends on the settings it needs.
  • Validation support, catch configuration errors early at startup instead of at runtime.
  • Flexibility, works not only with ASP.NET Core APIs but also with console apps, background services, and other .NET applications.

Adopting the Options Pattern is a small change in the way you structure configuration, but it pays off with cleaner design and fewer runtime surprises.

If you think your friends or network would find this article useful, please consider sharing it with them. Your support is greatly appreciated.

Thanks for reading!

Discover CodeSwissKnife Bar, your all-in-one, offline Developer Tools from Your Menu Bar

Leave A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.