HomeNikola Knezevic

In this article

Banner

Options Pattern in ASP.NET Core

02 Oct 2025
5 min

Sponsor Newsletter

The Options Pattern is the best way to bind, manage and validate configurations in .NET.

It allows you to access configuration through strongly typed classes, making your settings safer, easier to manage and more maintainable.

By grouping related settings into dedicated classes, your application benefits from two key principles:

  • Encapsulation
  • Separation of Concerns

In practice, classes depend only on the configuration values they actually use and different parts of the app remain independent, without unnecessary coupling between unrelated settings.

Additionally, the Options Pattern provides a built-in mechanism to validate configuration data, ensuring your app runs with correct and expected values.

Getting Started

Let’s start by examining the appsettings.json:

csharp
{
  "AllowedHosts": "*",
  "Postgres": {
    "DatabaseConnection": {
      "Username": "postgres",
      "Password": "postgres",
      "Host": "localhost",
      "Port": 5432,
      "Database": "postgres"
    }
  }
}

Here we can see a configuration section for PostgreSQL under Postgres:DatabaseConnection.

To represent this configuration in a strongly typed way, we’ll create a class:

csharp
public sealed class DatabaseOptions
{
    public string Username { get; init; }

    public string Password { get; init; }

    public string Host { get; init; }

    public int Port { get; init; }

    public string Database { get; init; }
}

With both the configuration and the options class in place, the next step is to bind them.

Configure Method

One common approach is to use the Configure method in the DI container:

csharp
builder.Services.Configure<DatabaseOptions>(
    builder.Configuration.GetSection("Postgres:DatabaseConnection"));

The Configure method tells the dependency injection container to bind the configuration section (Postgres:DatabaseConnection) to the DatabaseOptions class.

Configure Options

A second approach is useful if you want to move the configuration setup out of Program.cs and keep it more organized:

csharp
public class DatabaseOptionsSetup : IConfigureOptions<DatabaseOptions>
{
    private const string SectionName = "Postgres:DatabaseConnection";
    private readonly IConfiguration _configuration;

    public DatabaseOptionsSetup(IConfiguration configuration) =>
        _configuration = configuration;

    public void Configure(DatabaseOptions options) =>
        _configuration
            .GetSection(SectionName)
            .Bind(options);
}

Instead of binding directly in the startup file, you can encapsulate the binding logic inside a dedicated setup class.

By creating a class that implements IConfigureOptions<T>, you move all the binding logic into a single place. This keeps your Program.cs clean and focused only on registering services:

csharp
builder.Services.ConfigureOptions<DatabaseOptionsSetup>();

When the application starts, the DI container will automatically use DatabaseOptionsSetup to configure and bind the DatabaseOptions instance.

Injecting Options

Once your options are registered, you can access them anywhere in your application through dependency injection.

csharp
app.MapGet("options", (IOptions<DatabaseOptions> options) =>
    Results.Ok(options.Value));

The IOptions<T> interface provides a singleton snapshot of your configuration values. It’s ideal for scenarios where configuration doesn’t change while the application is running.

Depending on your needs, you can also use IOptionsSnapshot or IOptionsMonitor.

Key Mismatches

If, for some reason, the property names in your class don’t match the keys in appsettings.json, you have a few options:

  • Rename properties in your class to match the configuration keys.
  • Rename configuration keys to match the properties in your class.
  • Use attributes to map mismatched keys.
  • Perform manual binding with Bind.

Here's an example if you decide to decorate properties with attributes:

csharp
public sealed class DatabaseOptions
{
    [JsonPropertyName("username")]
    public string DbUsername { get; init; }

    [JsonPropertyName("password")]
    public string DbPassword { get; init; }

    public string Host { get; init; }

    public int Port { get; init; }

    public string Database { get; init; }
}

Here, username from configuration will map to DbUsername, and password will map to DbPassword.

If you prefer not to use attributes like me, you can bind mismatches directly in a lambda:

csharp
builder.Services.Configure<DatabaseOptions>(options =>
{
    var section = builder.Configuration.GetSection("Postgres:DatabaseConnection");

    // Normal binding for matching keys
    section.Bind(options);

    options.DbUsername = builder.Configuration["Postgres:DatabaseConnection:username"];
    options.DbPassword = builder.Configuration["Postgres:DatabaseConnection:password"];
});

This approach lets you combine automatic binding with manual overrides, giving you full control while still keeping things strongly typed.

Conclusion

The Options Pattern is a clean and powerful way to manage application settings in ASP.NET Core.

By binding configurations to strongly typed classes, you gain type safety, maintainability and flexibility, while keeping your codebase aligned with encapsulation and separation of concerns.

Additionally, with built-in validation support, you can ensure your application always starts with reliable and predictable values.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.