MutableOptions

MutableOptions

Extends .NET Options pattern to support writing back to IConfiguration while mutating strongly-typed options.

Usage

Register in dependency injection service container

ConfigureMutable method is provided, that basically can be used instead of Configure for options that needs to support mutations:

services.ConfigureMutable<SimpleOptions>(configurationRoot.GetSection("SimpleOptions"));

Resolve IOptionsMutator<SimpleOptions> to modify the configuration:

IOptionsMutator<SimpleOptions> optionsMutator
optionsMutator.Mutate(options => options with { Bar = 42 });

For convenience in situations when same code have to read and write options there is IMutableOptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IOptionsMutator<TOptions> interface. Both IOptionsMutator<TOptions> and IMutableOptionsMonitor<TOptions> are registered with singleton lifetime.

Installation

Install NuGet package from Package Manager Console:

PM> Install-Package MutableOptions

Features

  • Compatible with all standartd Options patter interfaces, e.g. IOptions<T>, IOptionsSnapshot<T>, IOptionsMonitor<T>, IConfigureOptions etc.
  • Named options are supported. Specify name parameter to IOptionsMutator.Mutate method to change value of named options.
  • Supports BinderOptions where it could be specified whether it can detect changed in non-public properties and write them to IConfiguration.
  • Can write values of most primitive types properties in flat options types. * **NOTE: Complex hierarchical options types, that contain nested classes or collections, is not supported! ***

Design considerations

IOptionsMutator is a core interface that allows changing configuration by mutating strongly typed options:

public interface IOptionsMutator<TOptions> where TOptions : class
{
    bool Mutate(string? name, Func<TOptions, TOptions> mutator);
}

, where mutator func should return new TOptions instance based on TOptions instance provided, changing the values of some or all properties.

It is recommended to define options types as immutable types, so it would not be possible to change state of TOptions instance directly. The easiest way to achieve that is to use records with init-only properties, e.g.:

public record SimpleOptions
{
    public string Foo { get; init; } = string.Empty;
    
    public int Bar { get; init; }
}

Record types also implement IEquatable<T>, which allows to configure mutable options without specifying custom IEqualityComparer<T>. Another usefult capability of record types is that they have support for with expressions to enable non-destructive mutation of records, which can be used to simplify mutator function implementtion.

optionsMutator.Mutate(options => options with { Bar = 42 });