OptionsProvider 0.2.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package OptionsProvider --version 0.2.1
                    
NuGet\Install-Package OptionsProvider -Version 0.2.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="OptionsProvider" Version="0.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OptionsProvider" Version="0.2.1" />
                    
Directory.Packages.props
<PackageReference Include="OptionsProvider" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add OptionsProvider --version 0.2.1
                    
#r "nuget: OptionsProvider, 0.2.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package OptionsProvider@0.2.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=OptionsProvider&version=0.2.1
                    
Install as a Cake Addin
#tool nuget:?package=OptionsProvider&version=0.2.1
                    
Install as a Cake Tool

OptionsProvider

Enables loading configurations from files to manage options for experiments or flights.

Features:

  • Each feature flag is represented by a JSON or YAML file which contains options to override default configuration values when processing feature names, flight names, or experiment names in a request. Note that YAML support is still experimental and parsing may change.
  • Reads separate files in parallel to keep independent configurations clear and easily maintainable.
  • Supports clear file names and aliases for feature names.
  • Uses the same logic that ConfigurationBuilder uses to load files so that it's easy to understand as it's the same as how appsettings*.json files are loaded.
  • Caching: Built configuration objects are cached by default in IMemoryCache to avoiding rebuilding the same objects for the same feature names.

Installation

dotnet add package OptionsProvider

See more at NuGet.org.

Example

Suppose you have a class that you want to use to configure your logic:

internal class MyConfiguration
{
    public string[]? Array { get; set; }
    public MyObject? Object { get; set; }
}

You probably already use Configuration in .NET and build an IConfiguration to use in your service based on some default settings in an appsettings.json file. Suppose you have an appsettings.json like this to configure MyConfiguration:

{
    "config": {
        "array": [
            "default item 1"
        ],
        "object": {
            "one": 1,
            "two": 2
        }
    },
    "another config": {
        ...
    }
}

Now you want to start experimenting with different values deep within MyConfiguration.

Create a new folder for configurations files, for this example, we'll call it Configurations and add some files to it. All *.json, *.yaml, and *.yml files in Configurations and any of its subdirectories will be loaded into memory.

Configurations/feature_A.json:

{
    "metadata": {
        "aliases": [ "a" ],
        "owners": "a-team@company.com"
    },
    "options": {
        "config": {
            "array": [
                "example item 1"
            ]
        }
    }
}

Configurations/feature_B/initial.yaml:

metadata:
    aliases:
        - "b"
    owners: "team-b@company.com"
options:
    config:
        array:
            - "different item 1"
            - "item 2"
        object:
            one: 11
            two: 22
            three: 3

When setting up your IServiceCollection for your service, do the following:

services
    .AddOptionsProvider("Configurations")
    .ConfigureOptions<MyConfiguration>("config")

There are two simple ways to get the right version of MyConfiguration for the current request based on the enabled features.

Using IOptionsProvider Directly

You can the inject IOptionsProvider into classes to get options for a given set of features. Features names are not case-sensitive.

using OptionsProvider;

class MyClass(IOptionsProvider optionsProvider)
{
    void DoSomething(...)
    {
        MyConfiguration options = optionsProvider.GetOptions<MyConfiguration>("config", ["A"]);
        // `options` be a result of merging the default values from appsettings.json, then applying Configurations/feature_A.json
        // because "a" is an alias for feature_A.json and aliases are case-insensitive.
    }
}

Using IOptionsSnapshot

Alternatively, you can also use IOptionsSnapshot<MyConfiguration> and follow .NET's Options pattern.

When a request starts, set the feature names based on the enabled features in your system (for example, the enabled features could be passed in a request body or from headers):

using OptionsProvider;

class MyController(IFeaturesContext context)
{
    public void InitializeContext(string[] enabledFeatures)
    {
        context.FeatureNames = enabledFeatures;
    }
}

Then while processing the request, IFeaturesContext will automatically be used to get the right configuration for the current request based on the enabled features. To use this method, MyConfiguration must have public setters for all of its properties.

In your code, you can use IOptionsSnapshot<MyConfiguration> to get the right configuration for the current request based on the enabled features:

class MyClass(IOptionsSnapshot<MyConfiguration> options)
{
    void DoSomething(...)
    {
        MyConfiguration options = options.Value;
    }
}

If enabledFeatures is ["A", "B"], then MyConfiguration will be built in this order:

  1. Apply the default values the injected IConfiguration, i.e. the values from appsettings.json under "config".
  2. Apply the values from Configurations/feature_A.json.
  3. Apply the values from Configurations/feature_B/initial.yaml.

Caching

["A", "B"] is treated the same as ["a", "FeAtuRe_B/iNiTiAl"] because using an alias is equivalent to using the path to the file and names and aliases are case-insensitive. Both examples would retrieve the same instance from the cache and IOptionsProvider would return the same instance. If IOptionsSnapshot<MyConfiguration> is used, then MyConfiguration will still only be built once and cached, but a different instance would be returned from IOptionsSnapshot<MyConfiguration>.Value for each scope because the options pattern creates a new instance each time.

Preserving Configuration Files

To ensure that the latest configuration files are used when running your service or tests, you may need to ensure the Configuration folder gets copied to the output folder. In your .csproj files with the Configurations folder, add a section like:

<ItemGroup>
  <Content Include="Configurations/**">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

or there are already rule about including files, but the configuration file for a feature isn't found, you can try:

<ItemGroup>
  <None Include="Configurations/**">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

Configuration Building Examples

Arrays/Lists

Note that ConfigurationBuilder does not concatenate lists, it merges them and overwrites entries because it treats each item in a list like a value in a dictionary indexed by the item's index.

For example, if the following features are applied:

Configurations/feature_A.yaml:

options:
  config:
    array:
      - 1
      - 2

Configurations/feature_B.yaml:

options:
  config:
    array:
      - 3

The resulting MyConfiguration for ["feature_A", "feature_B"] will have array set to [3, 2] because the second list is applied 'on top of' the first list. The builder views the lists as:

array from Configurations/feature_A.yaml:
array:0 = 1
array:1 = 2

array from Configurations/feature_B.yaml:
array:0 = 3

so the merged result is:
array:0 = 3
array:1 = 2

So array becomes [3, 2].

For more details, see here.

Development

Code Formatting

CI enforces:

dotnet format --verify-no-changes --severity info --no-restore src/*/*.sln

To automatically format code, run:

dotnet format --severity info --no-restore src/*/*.sln

Publishing

From the dotnet folder in the root of the repo, run:

api_key=<your NuGet API key>
cd src/OptionsProvider
dotnet pack --configuration Release
dotnet nuget push OptionsProvider/bin/Release/OptionsProvider.*.nupkg  --source https://api.nuget.org/v3/index.json -k $api_key --skip-duplicate
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.5.0 181 12/20/2024
1.4.0 144 12/20/2024
1.3.0 130 12/19/2024
1.2.2 162 12/5/2024
1.2.1 141 11/29/2024
1.2.0 138 11/28/2024
1.1.0 141 11/27/2024
1.0.1 154 11/25/2024
1.0.0 186 10/3/2024
0.6.0 145 10/2/2024
0.5.1 156 9/11/2024
0.5.0 170 9/11/2024
0.4.0 191 7/21/2024
0.3.0 141 7/9/2024
0.2.1 148 6/26/2024
0.2.0 148 6/24/2024
0.1.0 166 6/24/2024