AspNetCore.Docs/aspnetcore/fundamentals/configuration/options.md

23 KiB
Raw Blame History

title author description ms.author ms.custom ms.date uid
Options pattern in ASP.NET Core guardrex Discover how to use the options pattern to represent groups of related settings in ASP.NET Core apps. riande mvc 12/29/2018 fundamentals/configuration/options

Options pattern in ASP.NET Core

By Luke Latham

::: moniker range="<= aspnetcore-1.1"

For the 1.1 version of this topic, download Options pattern in ASP.NET Core (version 1.1, PDF).

::: moniker-end

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

Options also provide a mechanism to validate configuration data. For more information, see the Options validation section.

View or download sample code (how to download)

Prerequisites

Reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Options.ConfigurationExtensions package.

Options interfaces

xref:Microsoft.Extensions.Options.IOptionsMonitor`1 is used to retrieve options and manage options notifications for TOptions instances. xref:Microsoft.Extensions.Options.IOptionsMonitor`1 supports the following scenarios:

Post-configuration scenarios allow you to set or change options after all xref:Microsoft.Extensions.Options.IConfigureOptions`1 configuration occurs.

xref:Microsoft.Extensions.Options.IOptionsFactory`1 is responsible for creating new options instances. It has a single xref:Microsoft.Extensions.Options.IOptionsFactory`1.Create* method. The default implementation takes all registered xref:Microsoft.Extensions.Options.IConfigureOptions`1 and xref:Microsoft.Extensions.Options.IPostConfigureOptions`1 and runs all the configurations first, followed by the post-configuration. It distinguishes between xref:Microsoft.Extensions.Options.IConfigureNamedOptions`1 and xref:Microsoft.Extensions.Options.IConfigureOptions`1 and only calls the appropriate interface.

xref:Microsoft.Extensions.Options.IOptionsMonitorCache`1 is used by xref:Microsoft.Extensions.Options.IOptionsMonitor`1 to cache TOptions instances. The xref:Microsoft.Extensions.Options.IOptionsMonitorCache`1 invalidates options instances in the monitor so that the value is recomputed (xref:Microsoft.Extensions.Options.IOptionsMonitorCache`1.TryRemove*). Values can be manually introduced with xref:Microsoft.Extensions.Options.IOptionsMonitorCache`1.TryAdd*. The xref:Microsoft.Extensions.Options.IOptionsMonitorCache`1.Clear* method is used when all named instances should be recreated on demand.

xref:Microsoft.Extensions.Options.IOptionsSnapshot`1 is useful in scenarios where options should be recomputed on every request. For more information, see the Reload configuration data with IOptionsSnapshot section.

xref:Microsoft.Extensions.Options.IOptions`1 can be used to support options. However, xref:Microsoft.Extensions.Options.IOptions`1 doesn't support the preceding scenarios of xref:Microsoft.Extensions.Options.IOptionsMonitor`1. You may continue to use xref:Microsoft.Extensions.Options.IOptions`1 in existing frameworks and libraries that already use the xref:Microsoft.Extensions.Options.IOptions`1 interface and don't require the scenarios provided by xref:Microsoft.Extensions.Options.IOptionsMonitor`1.

General options configuration

General 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):

[!code-csharp]

The MyOptions class is added to the service container with xref:Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure* and bound to configuration:

[!code-csharp]

The following page model uses constructor dependency injection with xref:Microsoft.Extensions.Options.IOptionsMonitor`1 to access the settings (Pages/Index.cshtml.cs):

[!code-csharp]

[!code-csharp]

[!code-csharp]

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

[!code-json]

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

[!NOTE] When using a custom xref:System.Configuration.ConfigurationBuilder to load options configuration from a settings file, confirm that the base path is set correctly:

var configBuilder = new ConfigurationBuilder()
   .SetBasePath(Directory.GetCurrentDirectory())
   .AddJsonFile("appsettings.json", optional: true);
var config = configBuilder.Build();

services.Configure<MyOptions>(config);

Explicitly setting the base path isn't required when loading options configuration from the settings file via xref:Microsoft.AspNetCore.WebHost.CreateDefaultBuilder*.

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):

[!code-csharp]

In the following code, a second xref:Microsoft.Extensions.Options.IConfigureOptions`1 service is added to the service container. It uses a delegate to configure the binding with MyOptionsWithDelegateConfig:

[!code-csharp]

Index.cshtml.cs:

[!code-csharp]

[!code-csharp]

[!code-csharp]

You can add multiple configuration providers. Configuration providers are available from NuGet packages and are applied in the order that they're registered. For more information, see xref:fundamentals/configuration/index.

Each call to xref:Microsoft.Extensions.Options.IConfigureOptions`1.Configure* adds an xref:Microsoft.Extensions.Options.IConfigureOptions`1 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 scenario 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 xref:Microsoft.Extensions.Options.IConfigureOptions`1 service is added to the service container. It binds MySubOptions to the section subsection of the appsettings.json file:

[!code-csharp]

The GetSection extension method requires the Microsoft.Extensions.Options.ConfigurationExtensions NuGet package. If the app uses the Microsoft.AspNetCore.App metapackage (ASP.NET Core 2.1 or later), the package is automatically included.

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

[!code-json]

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

[!code-csharp]

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

[!code-csharp]

[!code-csharp]

[!code-csharp]

When the app is run, the OnGet method returns a string showing the suboption 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 xref:Microsoft.Extensions.Options.IOptionsMonitor`1 directly into a view (Pages/Index.cshtml.cs):

[!code-csharp]

[!code-csharp]

[!code-csharp]

The sample app shows how to inject IOptionsMonitor<MyOptions> with an @inject directive:

[!code-cshtml]

When the app is run, the options 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 xref:Microsoft.Extensions.Options.IOptionsSnapshot`1 is demonstrated in Example #5 in the sample app.

xref:Microsoft.Extensions.Options.IOptionsSnapshot`1 supports reloading options with minimal processing overhead.

Options are computed once per request when accessed and cached for the lifetime of the request.

The following example demonstrates how a new xref:Microsoft.Extensions.Options.IOptionsSnapshot`1 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.

[!code-csharp]

[!code-csharp]

[!code-csharp]

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 xref:Microsoft.Extensions.Options.IConfigureNamedOptions`1 is demonstrated as Example #6 in the sample app.

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

[!code-csharp]

The sample app accesses the named options with xref:Microsoft.Extensions.Options.IOptionsSnapshot`1.Get* (Pages/Index.cshtml.cs):

[!code-csharp]

[!code-csharp]

[!code-csharp]

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 options with the ConfigureAll method

Configure all options instances with the xref:Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.ConfigureAll* method. The following code configures Option1 for all configuration instances with a common value. Add the following code manually to the Startup.ConfigureServices 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] All options are named instances. Existing xref:Microsoft.Extensions.Options.IConfigureOptions`1 instances are treated as targeting the Options.DefaultName instance, which is string.Empty. xref:Microsoft.Extensions.Options.IConfigureNamedOptions`1 also implements xref:Microsoft.Extensions.Options.IConfigureOptions`1. The default implementation of the xref:Microsoft.Extensions.Options.IOptionsFactory`1 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 (xref:Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.ConfigureAll* and xref:Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.PostConfigureAll* use this convention).

OptionsBuilder API

xref:Microsoft.Extensions.Options.OptionsBuilder`1 is used to configure TOptions instances. OptionsBuilder streamlines creating named options as it's only a single parameter to the initial AddOptions<TOptions>(string optionsName) call instead of appearing in all of the subsequent calls. Options validation and the ConfigureOptions overloads that accept service dependencies are only available via OptionsBuilder.

// Options.DefaultName = "" is used.
services.AddOptions<MyOptions>().Configure(o => o.Property = "default");

services.AddOptions<MyOptions>("optionalName")
    .Configure(o => o.Property = "named");

Configure<TOptions, TDep1, ... TDep4> method

Using services from DI to configure options by implementing IConfigure[Named]Options in a boilerplate manner is verbose. Overloads for ConfigureOptions on OptionsBuilder<TOptions> allow you to use up to five services to configure options:

services.AddOptions<MyOptions>("optionalName")
    .Configure<Service1, Service2, Service3, Service4, Service5>(
        (o, s, s2, s3, s4, s5) => 
            o.Property = DoSomethingWith(s, s2, s3, s4, s5));

The overload registers a transient generic xref:Microsoft.Extensions.Options.IConfigureNamedOptions`1, which has a constructor that accepts the generic service types specified.

::: moniker range=">= aspnetcore-2.2"

Options validation

Options validation allows you to validate options when options are configured. Call Validate with a validation method that returns true if options are valid and false if they aren't valid:

// Registration
services.AddOptions<MyOptions>("optionalOptionsName")
    .Configure(o => { }) // Configure the options
    .Validate(o => YourValidationShouldReturnTrueIfValid(o), 
        "custom error");

// Consumption
var monitor = services.BuildServiceProvider()
    .GetService<IOptionsMonitor<MyOptions>>();
  
try
{
    var options = monitor.Get("optionalOptionsName");
}
catch (OptionsValidationException e) 
{
   // e.OptionsName returns "optionalOptionsName"
   // e.OptionsType returns typeof(MyOptions)
   // e.Failures returns a list of errors, which would contain 
   //     "custom error"
}

The preceding example sets the named options instance to optionalOptionsName. The default options instance is Options.DefaultName.

Validation runs when the options instance is created. Your options instance is guaranteed to pass validation the first time it's accessed.

[!IMPORTANT] Options validation doesn't guard against options modifications after the options are initially configured and validated.

The Validate method accepts a Func<TOptions, bool>. To fully customize validation, implement IValidateOptions<TOptions>, which allows:

  • Validation of multiple options types: class ValidateTwo : IValidateOptions<Option1>, IValidationOptions<Option2>
  • Validation that depends on another option type: public DependsOnAnotherOptionValidator(IOptionsMonitor<AnotherOption> options)

IValidateOptions validates:

  • A specific named options instance.
  • All options when name is null.

Return a ValidateOptionsResult from your implementation of the interface:

public interface IValidateOptions<TOptions> where TOptions : class
{
    ValidateOptionsResult Validate(string name, TOptions options);
}

Data Annotation-based validation is available from the Microsoft.Extensions.Options.DataAnnotations package by calling the ValidateDataAnnotations method on OptionsBuilder<TOptions>. Microsoft.Extensions.Options.DataAnnotations is included in the Microsoft.AspNetCore.App metapackage (ASP.NET Core 2.2 or later).

private class AnnotatedOptions
{
    [Required]
    public string Required { get; set; }

    [StringLength(5, ErrorMessage = "Too long.")]
    public string StringLength { get; set; }

    [Range(-5, 5, ErrorMessage = "Out of range.")]
    public int IntRange { get; set; }
}

[Fact]
public void CanValidateDataAnnotations()
{
    var services = new ServiceCollection();
    services.AddOptions<AnnotatedOptions>()
        .Configure(o =>
        {
            o.StringLength = "111111";
            o.IntRange = 10;
            o.Custom = "nowhere";
        })
        .ValidateDataAnnotations();

    var sp = services.BuildServiceProvider();

    var error = Assert.Throws<OptionsValidationException>(() => 
        sp.GetRequiredService<IOptionsMonitor<AnnotatedOptions>>().Value);
    ValidateFailure<AnnotatedOptions>(error, Options.DefaultName, 1,
        "DataAnnotation validation failed for members Required " +
            "with the error 'The Required field is required.'.",
        "DataAnnotation validation failed for members StringLength " +
            "with the error 'Too long.'.",
        "DataAnnotation validation failed for members IntRange " +
            "with the error 'Out of range.'.");
}

Eager validation (fail fast at startup) is under consideration for a future release.

::: moniker-end

Options post-configuration

Set post-configuration with xref:Microsoft.Extensions.Options.IPostConfigureOptions`1. Post-configuration runs after all xref:Microsoft.Extensions.Options.IConfigureOptions`1 configuration occurs:

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

xref:Microsoft.Extensions.Options.IPostConfigureOptions`1.PostConfigure* is available to post-configure named options:

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

Use xref:Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.PostConfigureAll* to post-configure all configuration instances:

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

Accessing options during startup

xref:Microsoft.Extensions.Options.IOptions`1 and xref:Microsoft.Extensions.Options.IOptionsMonitor`1 can be used in Startup.Configure, since services are built before the Configure method executes.

public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

Don't use xref:Microsoft.Extensions.Options.IOptions`1 or xref:Microsoft.Extensions.Options.IOptionsMonitor`1 in Startup.ConfigureServices. An inconsistent options state may exist due to the ordering of service registrations.

Additional resources