OptionsProvider 0.2.1
See the version list below for details.
dotnet add package OptionsProvider --version 0.2.1
NuGet\Install-Package OptionsProvider -Version 0.2.1
<PackageReference Include="OptionsProvider" Version="0.2.1" />
<PackageVersion Include="OptionsProvider" Version="0.2.1" />
<PackageReference Include="OptionsProvider" />
paket add OptionsProvider --version 0.2.1
#r "nuget: OptionsProvider, 0.2.1"
#:package OptionsProvider@0.2.1
#addin nuget:?package=OptionsProvider&version=0.2.1
#tool nuget:?package=OptionsProvider&version=0.2.1
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
ConfigurationBuilderuses to load files so that it's easy to understand as it's the same as howappsettings*.jsonfiles are loaded. - Caching: Built configuration objects are cached by default in
IMemoryCacheto 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:
- Apply the default values the injected
IConfiguration, i.e. the values fromappsettings.jsonunder"config". - Apply the values from
Configurations/feature_A.json. - 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 | Versions 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. |
-
net8.0
- Microsoft.Extensions.Caching.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Caching.Memory (>= 8.0.0)
- Microsoft.Extensions.Configuration (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.1)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
- YamlDotNet (>= 15.3.0)
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 |