Options pattern in ASP.NET Core

By Luke Latham

The options pattern uses options classes to represent groups of related settings. When configuration settings are isolated by feature into separate options classes, the app adheres to two important software engineering principles:

View or download sample code (how to download) This article is easier to follow with the sample app.

Basic options configuration

Basic options configuration is demonstrated as Example #1 in the sample app.

An options class must be non-abstract with a public parameterless constructor. The following class, MyOptions, has two properties, Option1 and Option2. Setting default values is optional, but the class constructor in the following example sets the default value of Option1. Option2 has a default value set by initializing the property directly (Models/MyOptions.cs):

public class MyOptions
{
    public MyOptions()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

The MyOptions class is added to the service container with IConfigureOptions<TOptions> and bound to configuration:

// Example #1: Basic options
// Register the ConfigurationBuilder instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);

The following page model uses constructor dependency injection with IOptions<TOptions> to access the settings (Pages/Index.cshtml.cs):

private readonly MyOptions _options;
public IndexModel(
    IOptions<MyOptions> optionsAccessor, 
    IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptions<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.Value;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
    _subOptions = subOptionsAccessor.Value;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #1: Simple options
var option1 = _options.Option1;
var option2 = _options.Option2;
SimpleOptions = $"option1 = {option1}, option2 = {option2}";

The sample's appsettings.json file specifies values for option1 and option2:

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  }
}

When the app is run, the page model's OnGet method returns a string showing the option class values:

option1 = value1_from_json, option2 = -1

Configure simple options with a delegate

Configuring simple options with a delegate is demonstrated as Example #2 in the sample app.

Use a delegate to set options values. The sample app uses the MyOptionsWithDelegateConfig class (Models/MyOptionsWithDelegateConfig.cs):

public class MyOptionsWithDelegateConfig
{
    public MyOptionsWithDelegateConfig()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

In the following code, a second IConfigureOptions<TOptions> service is added to the service container. It uses a delegate to configure the binding with MyOptionsWithDelegateConfig:

// Example #2: Options bound and configured by a delegate
services.Configure<MyOptionsWithDelegateConfig>(myOptions =>
{
    myOptions.Option1 = "value1_configured_by_delegate";
    myOptions.Option2 = 500;
});

Index.cshtml.cs:

private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
public IndexModel(
    IOptions<MyOptions> optionsAccessor, 
    IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptions<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.Value;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
    _subOptions = subOptionsAccessor.Value;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #2: Options configured by delegate
var delegate_config_option1 = _optionsWithDelegateConfig.Option1;
var delegate_config_option2 = _optionsWithDelegateConfig.Option2;
SimpleOptionsWithDelegateConfig = 
    $"delegate_option1 = {delegate_config_option1}, " +
    $"delegate_option2 = {delegate_config_option2}";

You can add multiple configuration providers. Configuration providers are available in NuGet packages. They're applied in order that they're registered.

Each call to Configure<TOptions> adds an IConfigureOptions<TOptions> service to the service container. In the preceding example, the values of Option1 and Option2 are both specified in appsettings.json, but the values of Option1 and Option2 are overridden by the configured delegate.

When more than one configuration service is enabled, the last configuration source specified wins and sets the configuration value. When the app is run, the page model's OnGet method returns a string showing the option class values:

delegate_option1 = value1_configured_by_delgate, delegate_option2 = 500

Suboptions configuration

Suboptions configuration is demonstrated as Example #3 in the sample app.

Apps should create options classes that pertain to specific feature groups (classes) in the app. Parts of the app that require configuration values should only have access to the configuration values that they use.

When binding options to configuration, each property in the options type is bound to a configuration key of the form property[:sub-property:]. For example, the MyOptions.Option1 property is bound to the key Option1, which is read from the option1 property in appsettings.json.

In the following code, a third IConfigureOptions<TOptions> service is added to the service container. It binds MySubOptions to the section subsection of the appsettings.json file:

// Example #3: Sub-options
// Bind options using a sub-section of the appsettings.json file.
services.Configure<MySubOptions>(Configuration.GetSection("subsection"));

The GetSection extension method requires the Microsoft.Extensions.Options.ConfigurationExtensions NuGet package. If the app uses the Microsoft.AspNetCore.All metapackage, the package is automatically included.

The sample's appsettings.json file defines a subsection member with keys for suboption1 and suboption2:

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  }
}

The MySubOptions class defines properties, SubOption1 and SubOption2, to hold the sub-option values (Models/MySubOptions.cs):

public class MySubOptions
{
    public MySubOptions()
    {
        // Set default values.
        SubOption1 = "value1_from_ctor";
        SubOption2 = 5;
    }
    
    public string SubOption1 { get; set; }
    public int SubOption2 { get; set; }
}

The page model's OnGet method returns a string with the sub-option values (Pages/Index.cshtml.cs):

private readonly MySubOptions _subOptions;
public IndexModel(
    IOptions<MyOptions> optionsAccessor, 
    IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptions<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.Value;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
    _subOptions = subOptionsAccessor.Value;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #3: Sub-options
var subOption1 = _subOptions.SubOption1;
var subOption2 = _subOptions.SubOption2;
SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";

When the app is run, the OnGet method returns a string showing the sub-option class values:

subOption1 = subvalue1_from_json, subOption2 = 200

Options provided by a view model or with direct view injection

Options provided by a view model or with direct view injection is demonstrated as Example #4 in the sample app.

Options can be supplied in a view model or by injecting IOptions<TOptions> directly into a view (Pages/Index.cshtml.cs):

private readonly MyOptions _options;
public IndexModel(
    IOptions<MyOptions> optionsAccessor, 
    IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptions<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.Value;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
    _subOptions = subOptionsAccessor.Value;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #4: Bind options directly to the page
MyOptions = _options;

For direct injection, inject IOptions<MyOptions> with an @inject directive:

@page
@model IndexModel
@using Microsoft.Extensions.Options
@using UsingOptionsSample.Models
@inject IOptions<MyOptions> OptionsAccessor
@{
    ViewData["Title"] = "Using Options Sample";
}

<h1>@ViewData["Title"]</h1>

When the app is run, the option values are shown in the rendered page:

Options values Option1: value1_from_json and Option2: -1 are loaded from the model and by injection into the view.

Reload configuration data with IOptionsSnapshot

Reloading configuration data with IOptionsSnapshot is demonstrated in Example #5 in the sample app.

Requires ASP.NET Core 1.1 or later.

IOptionsSnapshot supports reloading options with minimal processing overhead. In ASP.NET Core 1.1, IOptionsSnapshot is a snapshot of IOptionsMonitor<TOptions> and updates automatically whenever the monitor triggers changes based on the data source changing. In ASP.NET Core 2.0 and later, options are computed once per request when accessed and cached for the lifetime of the request.

The following example demonstrates how a new IOptionsSnapshot is created after appsettings.json changes (Pages/Index.cshtml.cs). Multiple requests to the server return constant values provided by the appsettings.json file until the file is changed and configuration reloads.

private readonly MyOptions _snapshotOptions;
public IndexModel(
    IOptions<MyOptions> optionsAccessor, 
    IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptions<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.Value;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
    _subOptions = subOptionsAccessor.Value;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #5: Snapshot options
var snapshotOption1 = _snapshotOptions.Option1;
var snapshotOption2 = _snapshotOptions.Option2;
SnapshotOptions = 
    $"snapshot option1 = {snapshotOption1}, " +
    $"snapshot option2 = {snapshotOption2}";

The following image shows the initial option1 and option2 values loaded from the appsettings.json file:

snapshot option1 = value1_from_json, snapshot option2 = -1

Change the values in the appsettings.json file to value1_from_json UPDATED and 200. Save the appsettings.json file. Refresh the browser to see that the options values are updated:

snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200

Named options support with IConfigureNamedOptions

Named options support with IConfigureNamedOptions is demonstrated as Example #6 in the sample app.

Requires ASP.NET Core 2.0 or later.

Named options support allows the app to distinguish between named options configurations. In the sample app, named options are declared with the ConfigureNamedOptions<TOptions>.Configure method:

// Example #6: Named options (named_options_1)
// Register the ConfigurationBuilder instance which MyOptions binds against.
// Specify that the options loaded from configuration are named
// "named_options_1".
services.Configure<MyOptions>("named_options_1", Configuration);

// Example #6: Named options (named_options_2)
// Specify that the options loaded from the MyOptions class are named
// "named_options_2".
// Use a delegate to configure option values.
services.Configure<MyOptions>("named_options_2", myOptions =>
{
    myOptions.Option1 = "named_options_2_value1_from_action";
});

The sample app accesses the named options with IOptionsSnapshot<TOptions>.Get (Pages/Index.cshtml.cs):

private readonly MyOptions _named_options_1;
private readonly MyOptions _named_options_2;
public IndexModel(
    IOptions<MyOptions> optionsAccessor, 
    IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptions<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.Value;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
    _subOptions = subOptionsAccessor.Value;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #6: Named options
var named_options_1 = 
    $"named_options_1: option1 = {_named_options_1.Option1}, " +
    $"option2 = {_named_options_1.Option2}";
var named_options_2 = 
    $"named_options_2: option1 = {_named_options_2.Option1}, " +
    $"option2 = {_named_options_2.Option2}";
NamedOptions = $"{named_options_1} {named_options_2}";

Running the sample app, the named options are returned:

named_options_1: option1 = value1_from_json, option2 = -1
named_options_2: option1 = named_options_2_value1_from_action, option2 = 5

named_options_1 values are provided from configuration, which are loaded from the appsettings.json file. named_options_2 values are provided by:

  • The named_options_2 delegate in ConfigureServices for Option1.
  • The default value for Option2 provided by the MyOptions class.

Configure all named options instances with the OptionsServiceCollectionExtensions.ConfigureAll method. The following code configures Option1 for all named configuration instances with a common value. Add the following code manually to the Configure method:

services.ConfigureAll<MyOptions>(myOptions => 
{
    myOptions.Option1 = "ConfigureAll replacement value";
});

Running the sample app after adding the code produces the following result:

named_options_1: option1 = ConfigureAll replacement value, option2 = -1
named_options_2: option1 = ConfigureAll replacement value, option2 = 5

Note

In ASP.NET Core 2.0 and later, all options are named instances. Existing IConfigureOption instances are treated as targeting the Options.DefaultName instance, which is string.Empty. IConfigureNamedOptions also implements IConfigureOptions. The default implementation of the IOptionsFactory<TOptions> (reference source) has logic to use each appropriately. The null named option is used to target all of the named instances instead of a specific named instance (ConfigureAll and PostConfigureAll use this convention).

IPostConfigureOptions

Requires ASP.NET Core 2.0 or later.

Set postconfiguration with IPostConfigureOptions<TOptions>. Postconfiguration runs after all IConfigureOptions<TOptions> configuration occurs:

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure<TOptions> is available to post-configure named options:

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Use PostConfigureAll<TOptions> to post-configure all named configuration instances:

services.PostConfigureAll<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Options factory, monitoring, and cache

IOptionsMonitor is used for notifications when TOptions instances change. IOptionsMonitor supports reloadable options, change notifications, and IPostConfigureOptions.

IOptionsFactory<TOptions> (ASP.NET Core 2.0 or later) is responsible for creating new options instances. It has a single Create method. The default implementation takes all registered IConfigureOptions and IPostConfigureOptions and runs all the configures first, followed by the post-configures. It distinguishes between IConfigureNamedOptions and IConfigureOptions and only calls the appropriate interface.

IOptionsMonitorCache<TOptions> (ASP.NET Core 2.0 or later) is used by IOptionsMonitor to cache TOptions instances. The IOptionsMonitorCache invalidates options instances in the monitor so that the value is recomputed (TryRemove). Values can be manually introduced as well with TryAdd. The Clear method is used when all named instances should be recreated on demand.

See also