More Options Crate
This library contains all of the fundamental abstractions for defining configuration options.
Features
This crate provides the following features:
- Default - Abstractions for the options
- di - Provides dependency injection extensions
- cfg - Provides dependency injection extensions to bind configurations to options
- async - Provides features for using options in an asynchronous context
Options Pattern
The options pattern uses classes to provide strongly typed access to groups of related settings. Options also provide a mechanism to validate configuration data. For more information, see the Options validation section.
Bind Hierarchical Configuration
The preferred way to read related configuration values is using the options pattern. For example, to read the following configuration values:
"Position":
Create the following PositionOptions struct:
An options struct:
- Must be public.
- Should implement the
Defaulttrait; otherwise a customOptionsFactory<TOptions>is required. - All public read-write fields of the type are bound.
The following code:
- Calls
ConfigurationBinder.bindto bind thePositionOptionsclass to thePositionsection. - Displays the
Positionconfiguration data. - Requires the binder feature to be enabled
- Which transitively enables the serde feature
pub
ConfigurationBinder.reify<T> binds and returns the specified type. ConfigurationBinder.reify<T> may be more convenient than using ConfigurationBinder.bind. The following code shows how to use ConfigurationBinder.reify<T> with the PositionOptions struct:
pub
An alternative approach when using the options pattern is to bind the
Position section and add it to the dependency injection service container.
In the following code, PositionOptions is added to the service container with
configure and bound to configuration:
pub TestModel
Options Traits
Options<TOptions>:
- Does not support:
- Reading of configuration data after the app has started.
- Is registered as a
Singletonand can be injected into any service lifetime.
OptionsSnapshot<TOptions>:
- Is useful in scenarios where options should be recomputed on every request.
- Is registered as
Scopedand therefore can't be injected into aSingletonservice.
OptionsMonitor<TOptions>:
- Is used to retrieve options and manage options notifications for
TOptionsinstances. - Is registered as a
Singletonand can be injected into any service lifetime. - Supports:
- Change notifications
- Reloadable configuration
- Selective options invalidation (
OptionsMonitorCache<TOptions>)
Post-configuration scenarios enable setting or changing
options after all ConfigureOptions<TOptions> configuration occurs.
OptionsFactory<TOptions> is responsible for creating new options instances. It has a single
create method. The default implementation takes all registered ConfigureOptions<TOptions>
and PostConfigureOptions<TOptions> and runs all the configurations first, followed by the
post-configuration.
OptionsMonitorCache<TOptions> is used by OptionsMonitor<TOptions>to cache TOptions instances.
The OptionsMonitorCache<TOptions> invalidates options instances in the monitor so that the value
is recomputed (try_remove). Values can be manually introduced with try_add. The clear method
is used when all named instances should be recreated on demand.
Use OptionsSnapshot to Read Updated Data
Using OptionsSnapshot<TOptions>:
- Options are computed once per request when accessed and cached for the lifetime of the request.
- May incur a significant performance penalty because it's a
Scopedservice and is recomputed per request. - Changes to the configuration are read after the app starts when using configuration providers that support reading updated configuration values.
The difference between OptionsMonitor<TOptions> and OptionsSnapshot<TOptions> is that:
OptionsMonitor<TOptions>is aSingletonservice that retrieves current option values at any time, which is especially useful in singleton dependencies.OptionsSnapshot<TOptions>is aScopedservice and provides a snapshot of the options at the time theOptionsSnapshot<TOptions>object is constructed. Options snapshots are designed for use with transient and scoped dependencies.
The following code uses OptionsSnapshot<TOptions>:
pub TestSnapModel
OptionsMonitor
The following code registers a configuration instance which MyOptions binds against:
pub TestMonitorModel
Use Dependency Injection to Configure Options
Services can be accessed from dependency injection while configuring options in two ways:
- Pass a configuration function
services.
.configure;
services.;
services.
.configure5;
- Implement the
ConfigureOptions<TOptions>trait and register it as a service
It is recommended to pass a configuration closure to one of the configure functions
since creating a struct is more complex. Creating a struct is equivalent to what the
framework does when calling any of the configure functions. Calling one of the
configure functions registers a transient ConfigureOptions<TOptions>, which
initializes with the specified service types.
| Function | Description |
|---|---|
configure |
Configures the options without using any services |
configure1 |
Configures the options using a single dependency |
configure2 |
Configures the options using 2 dependencies |
configure3 |
Configures the options using 3 dependencies |
configure4 |
Configures the options using 4 dependencies |
configure5 |
Configures the options using 5 dependencies |
post_configure |
Post-configures the options without using any services |
post_configure1 |
Post-configures the options using a single dependency |
post_configure2 |
Post-configures the options using 2 dependencies |
post_configure3 |
Post-configures the options using 3 dependencies |
post_configure4 |
Post-configures the options using 4 dependencies |
post_configure5 |
Post-configures the options using 5 dependencies |
validate |
Validates the options without using any services |
validate1 |
Validates the options using a single dependency |
validate2 |
Validates the options using 2 dependencies |
validate3 |
Validates the options using 3 dependencies |
validate4 |
Validates the options using 4 dependencies |
validate5 |
Validates the options using 5 dependencies |
Options Validation
Options validation enables option values to be validated.
Consider the following appsettings.json file:
The following code:
- Calls
add_optionsto get anOptionsBuilder<TOptions>that binds to theMyConfigOptionsstruct. - Invokes a closure to validate the struct.
It is recommended to validate options via a closure as opposed to implementing
ValidateOptions<TOptions> directly. The default validation consumers, such as
OptionsFactory<TOptions>, panic if there are any validation errors as the
application is considered to be in an invalid state.
ValidateOptions<TOptions>
The following struct implements ValidateOptions<TOptions>:
;
ValidateOptions enables moving the validation code out of a closure and into a struct.
Using the preceding code, validation is enabled with the following code:
Options Post-Configuration
Set post-configuration with PostConfigureOptions<TOptions>. Post-configuration
runs after all ConfigureOptions<TOptions> configuration occurs:
post_configure_options applies to all instances. To apply a named configuration use
post_configure_named_options.
License
This project is licensed under the MIT license.