20 KiB
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 | 11/28/2017 | fundamentals/configuration/options |
Options pattern in ASP.NET Core
By Luke Latham
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:
- The Interface Segregation Principle (ISP): Scenarios (classes) that depend on configuration settings depend only on the configuration settings that they use.
- Separation of Concerns: Settings for different parts of the app aren't dependent or coupled to one another.
View or download sample code (how to download) This article is easier to follow with the sample app.
Prerequisites
::: moniker range=">= aspnetcore-2.1"
Reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Options.ConfigurationExtensions package.
::: moniker-end
::: moniker range="= aspnetcore-2.0"
Reference the Microsoft.AspNetCore.All metapackage or add a package reference to the Microsoft.Extensions.Options.ConfigurationExtensions package.
::: moniker-end
::: moniker range="< aspnetcore-2.0"
Add a package reference to the Microsoft.Extensions.Options.ConfigurationExtensions package.
::: moniker-end
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):
The MyOptions
class is added to the service container with Configure<TOptions>(IServiceCollection, IConfiguration) and bound to configuration:
The following page model uses constructor dependency injection with IOptions<TOptions> to access the settings (Pages/Index.cshtml.cs):
The sample's appsettings.json file specifies values for option1
and option2
:
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 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 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):
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
:
Index.cshtml.cs:
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 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 IConfigureOptions<TOptions>
service is added to the service container. It binds MySubOptions
to the section subsection
of the appsettings.json file:
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
:
The MySubOptions
class defines properties, SubOption1
and SubOption2
, to hold the options values (Models/MySubOptions.cs):
The page model's OnGet
method returns a string with the options values (Pages/Index.cshtml.cs):
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):
For direct injection, inject IOptions<MyOptions>
with an @inject
directive:
When the app is run, the options values are shown in the rendered page:
::: moniker range=">= aspnetcore-1.1"
Reload configuration data with IOptionsSnapshot
Reloading configuration data with IOptionsSnapshot
is demonstrated in Example #5 in the sample app.
IOptionsSnapshot supports reloading options with minimal processing overhead.
::: moniker-end
::: moniker range=">= aspnetcore-2.0"
Options are computed once per request when accessed and cached for the lifetime of the request.
::: moniker-end
::: moniker range="< aspnetcore-2.0"
IOptionsSnapshot
is a snapshot of IOptionsMonitor<TOptions> and updates automatically whenever the monitor triggers changes based on the data source changing.
::: moniker-end
::: moniker range=">= aspnetcore-1.1"
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.
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
::: moniker-end
::: moniker range=">= aspnetcore-2.0"
Named options support with IConfigureNamedOptions
Named options support with IConfigureNamedOptions 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 the OptionsServiceCollectionExtensions.Configure<TOptions>(IServiceCollection, String, Action<TOptions>) which in turn calls the extension method ConfigureNamedOptions<TOptions>.Configure method:
The sample app accesses the named options with IOptionsSnapshot<TOptions>.Get (Pages/Index.cshtml.cs):
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 inConfigureServices
forOption1
. - The default value for
Option2
provided by theMyOptions
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] All options are named instances. Existing
IConfigureOption
instances are treated as targeting theOptions.DefaultName
instance, which isstring.Empty
.IConfigureNamedOptions
also implementsIConfigureOptions
. The default implementation of the IOptionsFactory<TOptions> (reference source has logic to use each appropriately. Thenull
named option is used to target all of the named instances instead of a specific named instance (ConfigureAll and PostConfigureAll use this convention).
::: moniker-end
::: 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(IOptions<AnotherOption> options)
IValidateOptions
validates:
- A specific named options instance.
- All options when
name
isnull
.
Return a ValidateOptionsResult
from your implementation of the interface:
public interface IValidateOptions<TOptions> where TOptions : class
{
ValidateOptionsResult Validate(string name, TOptions options);
}
Eager validation (fail fast at startup) and data annotation-based validation are scheduled for a future release.
::: moniker-end
::: moniker range=">= aspnetcore-2.0"
IPostConfigureOptions
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>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
::: moniker-end
Options factory, monitoring, and cache
IOptionsMonitor is used for notifications when TOptions
instances change. IOptionsMonitor
supports reloadable options, change notifications, and IPostConfigureOptions
.
::: moniker range=">= aspnetcore-2.0"
IOptionsFactory<TOptions> 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> 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.
::: moniker-end
Accessing options during startup
IOptions
can be used in Startup.Configure
, since services are built before the Configure
method executes.
public void Configure(IApplicationBuilder app, IOptions<MyOptions> optionsAccessor)
{
var option1 = optionsAccessor.Value.Option1;
}
IOptions
shouldn't be used in Startup.ConfigureServices
. An inconsistent options state may exist due to the ordering of service registrations.