Only this pageAll pages
Powered by GitBook
1 of 64

Vostok.Configuration

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Configuration sources

Configuration sources fetch data from storage (local files or remote APIs) and convert it to settings nodes, abstracting away actual data formats such as JSON or YAML.

They are not meant to be consumed directly and should be used in conjunction with a configuration provider (see assign sources to types and obtain settings from provider scenarios).

Sources are also responsible for data change detection. They expose a reactive interface with subscription support:

IObservable<(ISettingsNode settings, Exception error)> Observe();

On each update, triggered either periodically or by an internal event, the source emits a pair: (settings, null) on success or (null, error) on failure. It's not required to deduplicate settings or errors at this level, although it's never wrong to do so.

Sources must never block indefinitely while waiting for data and should rather publish null settings after a short initial timeout.

Sources must be thread-safe and should be designed to support multiple concurrent observers. It is also expected that every new observer would immediately receive a notification with current state upon subscription.

Here are some of the often used source implementations:

It's also possible to .

Related pages

Settings nodes scoping

Scoping is the operation of navigating a tree by accessing child nodes of via names in a case-insensitive manner. A sequence of names resembling a path in the object structure, such as ["property1", "property2"] is called a scope.

Scoping does not work on and nodes (always results in null).

Scoping is used to map object fields/properties to nodes of settings tree during . It also allows to — create a source that returns a subtree of settings returned by the original source.

JSON source
ClusterConfig source
In-memory object source
implement a custom source
Sources
Combine sources
Scope sources
Nest sources
Transform sources
Create custom sources
Assign sources to types
Obtain settings from provider
Examples
  • {A: 1} scoped to a is just a value of 1.

  • {A: 1} scoped to b is null.

  • {A: {B: [1, 2]}} scoped to A is {B: [1, 2]}

  • {A: {B: [1, 2]}} scoped to [A, B] is [1, 2]

  • {A: {B: [1, 2]}} scoped to [A, B, C] is null.

Related pages

settings node
objects
value
array
binding
scope sources
Scope sources
Binding nodes to models

Command line source

Location: main sources module.

CommandLineSource converts CLI arguments into settings nodes.

It supports 7 syntax options for key-value parameters:

  • --key=value

  • --key value

  • -key=value

  • -key value

  • /key=value

  • /key value

  • key=value

Keys with dots (such as a.b.c) are treated as hierarchical and get split into segments:

Multiple occurrences of the same key are merged into arrays.

This source is static and never issues data updates.

Default keys and values

Standalone keys may optionally be supplied with a default value.

Standalone values may optionally be grouped under default key.

Related pages

public static void Main(string[] args)
{
    var source = new CommandLineSource(args);
}
Configuration sources
--throttling.capacity=50 --> { "throttling": { "capacity": "50" } }
var source = new CommandLineSource(args, "main", "true");

--enable --> { "enable": "true" }
100 200 --> { "main": ["100", "200"] }

Environment variables source

Location: main sources module.

EnvironmentVariablesSource converts process environment variables into settings nodes.

var source = new EnvironmentVariablesSource();

Keys with dots, colons and double underscores are treated as hierarchical and get split into segments:

throttling.capacity=50 --> { "throttling": { "capacity": "50" } }
throttling:capacity=50 --> { "throttling": { "capacity": "50" } }
throttling__capacity=50 --> { "throttling": { "capacity": "50" } }

This source is static and never issues data updates.

Related pages

Configuration sources

Log errors

Requires: logging module.

See error handling for cases where this logging is useful.

See logging documentation for the log interface required here.

var providerSettings = new ConfigurationProviderSettings();

providerSettings = providerSettings.WithErrorLogging(log);

var provider = new ConfigurationProvider(providerSettings);

Secret settings should be marked with an attribute to avoid being logged.

Obtain settings from provider

Requires: main module.

Settings can be obtained on-demand with configuration provider's Get method.

With prior assignment of sources:

var settings = provider.Get<MySettings>();

With sources passed on per-call basis:

Get method behavior

  • Always returns the most recent version of settings (updates may happen in background):

    • Call Get on every settings access for "hot" configuration;

    • Call Get once and cache the result for "cold" configuration;

  • First call for a type may block or due to source latency, data unavailability or incorrect data format. This behavior persists until a data update remedies the error;

  • Once warmed up, subsequent calls never block or throw errors and are extremely cheap due to . Future errors are not propagated to the calling code, but . Get calls return the last seen correct settings object;

Related pages

var settings = provider.Get<MySettings>(new JsonFileSource("settings.json"));
throw exceptions
caching
can be logged
Configuration provider
Configuration sources
Caching and performance
Error handling

Print settings

Requires: main module.

Settings models can be printed in JSON or YAML formats to generate configuration file examples.

var printSettings = new PrintSettings 
{
    Format = PrintFormat.Json
};

var defaultSettings = new MySettings();

var printed = ConfigurationPrinter.Print(defaultSettings);

Console.Out.WriteLine(printed);

Observe settings via provider

Requires: main module.

One can subscribe to settings updates for a type with configuration provider's Observe method.

With prior assignment of sources:

With sources passed on per-call basis:

Temporary subscriptions should be disposed of as they might hold on to resources in sources:

Observe method behavior

  • OnError and OnCompleted notifications are never produced. Only successful settings updates are propagated to the observers. This means that there will be no notifications if initial attempt to provide settings and no further data updates are published by the .

    • The only way to notice errors when using Observe is to .

Best practices

It's recommended to obtain initial settings instance with Get method before subscribing to updates with Observe. This practice ensures correct error propagation, warms up the and eliminates situations where the settings are not ready yet on access attempt.

Related pages

provider.Observe<MySettings>().Subscribe(newSettings => {});
provider.Observe<MySettings>(new JsonFileSource("settings1.json"))
    .Subscribe(newSettings => {});
var subscription = provider.Observe<MySettings>().Subscribe(OnNewSettings);

using (subscription)
{
    // ...
}

The subscription is not guaranteed to immediately produce a notification. The first notification may be delayed due to data not having been fetched from the source yet. However, once a valid settings instance has been observed, all new observers receive a notification with current actual settings upon subscription. This notification is published on a background thread, so don't count on it being delivered after Subscribe call completes.

fails with an error
source
enable error logging
cache
Configuration provider
Configuration sources
Caching and performance
Error handling
private volatile MySettings settings;
 
public Task InitializeAsync(IConfigurationProvider provider)
{
    OnSettingsUpdated(settings = provider.Get<MySettings>());
         
    provider.Observe<MySettings>()
      .Subscribe(newSettings => OnSettingsUpdated(settings = newSettings));
 
    return Task.CompletedTask;
}
 
private void OnSettingsUpdated(MySettings settings) 
{
    // ... 
}

Make settings required

Requires: abstractions module.

By default all settings are optional; that is, absence of relevant data in the source results in default values during binding and does not produce errors.

Some parts of configuration may be crucial to the application to the point that it's pointless to start without them being initialized. These fields and properties should be annotated with [Required] attribute:

class MySettings 
{
    [Required]
    public string DbConnectionString { get; }
    
    [Required]
    public MySecrets Secrets { get; }
}

class MySecrets 
{
    public string ApiKey { get; }
    public string Login { get; }
    public string Password { get; }
}

Changing defaults

Default behavior can be inverted in the scope of a type with [RequiredByDefault] attribute. Individual fields and properties can then be made optional with [Optional] attribute:

Related pages

provider.Get<MySettings>(); // will throw if DbConnectionString or Secrets are not initialized 
Obtain settings from provider
Observe settings via provider
Binding nodes to models
[RequiredByDefault]
class MySettings 
{
    [Optional]
    public TimeSpan Timeout { get; } = TimeSpan.FromSeconds(30);
}

Transform sources

Requires: sources module.

This operation allows to modify source contents by applying a custom delegate. Most often this involves rewriting values stored in value nodes:

class MyTransformer : ValueNodeTransformer
{
    public MyTransformer()
        : base(node => new ValueNode(node.Name, node.Value?.Replace(..., ...)))
    {
    }
}
var transformedSource = source.Transform(new MyTransformer());

Related pages

Configuration sourcesSettings nodesUse value substitutions

Concepts and basics

Home

Vostok.Configuration in a nutshell

Vostok.Configuration is a set of libraries offering configuration tools to .NET applications. It handles fetching configuration data from files or APIs, parsing and converting it to user-defined model classes.

Guiding design principles

  • Configuration data sources should be composable regardless of storage formats.

    • It should be easy to combine settings from JSON files, environment variables and custom APIs. This is achieved with concept and universal abstraction.

  • "Hot" configuration updates should be easy to leverage.

Features

  • and methods to obtain settings;

  • Support for and settings;

  • Support for custom settings ;

Guarantees

See / settings scenarios, and sections for details.

Good starting points

  • Hot configuration implies handling changes in settings without application restart. This is enabled by explicitly reactive source design and settings provider methods.

  • Rich object models should be supported for user convenience.

    • Binding process supports a wide range of primitives, collections, classes, interfaces and employs a number of conventions for arbitrary types (anything with a TryParse method works).

  • It should be possible to extend the library with arbitrary data sources and object models.

    • Two major extensions points are custom configuration sources and binders.

  • Wide selection of built-in sources;

  • Composable binders;

  • Sources can be combined, navigated and transformed;

  • Integration with Microsoft configuration system;

  • configuration source
    settings nodes
    On-demand
    subscription-based
    required
    secret
    validation
    obtain
    observe
    caching
    error handling
    Quickstart
    Concepts and basics
    Basic scenarios
    Modules

    Value nodes

    Value nodes are key-value pairs with optional keys. They cannot have child nodes and thus are always the leaves of settings node trees. Standalone values are rare: most of the time value nodes can be found inside objects or arrays.

    Value nodes are typically mapped to primitive types during binding.

    Properties

    Property

    Equality

    Two value nodes are considered equal if their values match exactly and their names match up to differences in case.

    Representation

    Related pages

    Description

    Name

    Required if nested in an object node, optional otherwise.

    Value

    Useful payload. The value of a object field or array element. Can be null.

    Children

    Always returns an empty sequence.

    this[name]

    Always returns null.

    Settings nodes
    Settings nodes merging
    Value("name", "value") --> "value"
    Value("name", null) --> <null>

    Array nodes

    are containers used to represent sequences, such as JSON arrays. Each array node contains an ordered list of child nodes. There is no limit to nesting: arrays can contain other arrays and objects. Elements of an array are not required to have names.

    Array nodes are typically mapped to ordered collections during .

    Properties

    Name

    Required if nested in an , optional otherwise.

    Value

    Always returns null. Only can have values.

    Children

    Returns an ordered sequence of child nodes.

    ChildrenCount

    Returns the number of elements in the Children sequence.

    this[name]

    Always returns null. Arrays cannot be navigated with .

    Equality

    Two array nodes are considered equal if their Children sequences are equal elementwise and their names match up to differences in case.

    Representation

    Related pages

    roperty

    Array nodes
    binding

    Description

    Settings nodes
    Settings nodes merging
    Array(Value("1"), Value("2)) --> ["1", "2"]
    Array(Object(), Object()) --> [{}, {}]
    Array(Array(Value("1")), Array(Value("2"))) --> [["1"], ["2"]]
    object node
    value nodes
    scoping

    Quickstart

    Here's the simplest way to experience Vostok.Configuration for the first time:

    • Install main, abstractions and sources.json modules:

    Install-Package Vostok.Configuration
    Install-Package Vostok.Configuration.Abstractions
    Install-Package Vostok.Configuration.Sources.Json
    • Define a settings model class:

    class MySettings 
    {
        public string Key1 { get; }
        public string Key2 { get; }
    }
    • Create a JSON file with settings:

    • Obtain a model instance with a from a file and print it:

    Great job! Here are some possible next steps:

    • Learn the most basic concepts: , , , ;

    • Explore available and ;

    • Go over the .

    Configuration provider

    Overview

    is responsible for the and offers methods to obtain final settings models. It's also responsible for and . Providers are used directly by the application code to either or .

    Settings nodes

    Settings nodes are the intermediate representation of configuration data and one of the core concepts of the library. Their main purpose is to abstract away various configuration formats (JSON, XML, etc.) so that can be implemented once without regard to the nature of being used.

    Key points

    • A settings node is a tree with string keys and string values in its leaf nodes.

    configuration provider
    source
    settings nodes
    sources
    binding
    provider
    modules
    source implementations
    basic scenarios section
    Methods

    Get method

    Fetches the newest version of settings of given type:

    Observe method

    Allows to subscribe for updates of settings of given type:

    Overloads

    Both Get and Observe methods have 2 variations:

    • The one without any parameters requires a prior assignment of a source to the requested type;

    • The one with a source parameter requires no such assignment (check out caching for gotchas);

    Related pages

    Configuration provider
    binding process
    caching
    error handling
    obtain settings on demand
    subscribe to updates
    Obtain settings from provider
    Observe settings via provider
    Binding nodes to models
    Configuration sources
    Assign sources to types
    Caching and performance
    Error handling
    {
        "key1": "value1",
        "key2": "value2"
    }
    var provider = new ConfigurationProvider();
    
    provider.SetupSourceFor<MySettings>(new JsonFileSource("settings.json"));
    
    var settings = provider.Get<MySettings>();
    
    Console.Out.WriteLine(ConfigurationPrinter.Print(settings));
    var settings = provider.Get<MySettings>();
    provider.Observe<MySettings>.Subscribe(newSettings => {});

    Configuration sources produce settings nodes as their primary artifacts.

  • Binding is the process of converting a settings node to an arbitrary .NET object.

  • Settings nodes are implemented in the abstractions module.

  • Settings nodes are immutable objects.

  • Why is it necessary to learn about settings nodes?

    Despite being a somewhat internal API used directly only in a handful of advanced scenarios, settings nodes are crucial for a solid understanding of how configuration data translates to objects in C# code via different node types and scoping.

    When is there a need to use settings nodes directly?

    • Implementation of a custom configuration source;

    • Implementation of a custom model binder;

    • Transformation of configuration sources

    Node types

    There are three types of nodes:

    • Value nodes, used to hold data;

    • Array nodes, used to represent sequences;

    • Object nodes, used to represent objects with named properties.

    Users are not expected to implement custom node types.

    Null node instances represent absence of settings (e.g. a configuration file that does not exist).

    Node interface

    All node types implement the ISettingsNode interface and have following properties:

    Property

    Type

    Description

    Name

    string

    Node name. Case-insensitive.

    Value

    string

    Node value. Only present in .

    Children

    IEnumerable<ISettingsNode>

    Sequence of child nodes in containers: and .

    this[name]

    All nodes also implement a merge operation.

    Representation

    All node types implement a JSON-like ToString() method. Its result may look like this for a sample object node with two nested value nodes:

    This representation is extensively used on the rest of the pages.

    Related pages

    binding to model classes
    configuration sources
    Value nodes
    Array nodes
    Object nodes
    Settings nodes merging
    Settings nodes scoping
    Configuration sources
    Binding nodes to models

    Settings nodes merging

    Merge is the operation of reducing two to one: left + right --> result.

    Its primary use is to .

    API

    Each node type implements a Merge method that accept another node (right in our terms) and an instance of merge options with following properties:

    Binding nodes to models

    Binding is the process of initializing a model (an instance of almost arbitrary type) with data from a obtained from a :

    node {A: 1, B: 2} --> new CustomModel { A = 1, B = 2}

    The resulting model is queried by the application code with a to access settings.

    Binders

    Binding is implemented by a set of , each of which knows how to convert a

    Sources.Json

    Description

    This library contains configuration implementations: and . It also exposes a configuration for integration with other sources, such as .

    Error handling

    This page describes how deal with errors arising from and .

    It can be summarized in two simple rules:

    • If an error occurs and no correct settings instance has been observed for the requested type thus far, the error is propagated to the calling code, resulting in exceptions from Get method. A subsequent settings update with correct data automatically "heals" future Get calls.

    Object nodes

    are containers used to represent objects with named fields/properties. Each object node contains a map of child nodes with their names as keys. There is no limit to nesting: objects can contain other objects and arrays. Elements of an object are required to have non-null names.

    Object nodes are typically mapped to arbitrary classes and structs during .

    Properties

    Caching and performance

    cache settings for each (type, source) pair where sources are compared by reference. Caching ensures a solid performance level: only the first Get call is somewhat expensive while all the subsequent ones are extremely cheap. The cache is automatically updated when the underlying issues new data.

    Due to caching, instances should be reused as much as possible. Ideally there should be just one singleton instance in the application.

    {
       "A": "1",
       "B": "2"
    }

    ISettingsNode

    Name-based indexer used to navigate objects (see scoping).

    value nodes
    arrays
    objects

    Special care should be taken when using Get and Observe methods with short-lived source instances passed on per-call basis. This could cause poor performance due to cache misses and even lead to cache overflow events. Overflow events may cause violations of error handling guarantees. Default cache capacity is 50 but can be tuned in provider settings:

    This pitfall is easy to fall into as all of the source-related extensions (combine, scope, transform, etc) return decorators that are treated as distinct sources. The only viable solution is to cache these derivative sources.

    Related pages

    Configuration providers
    bound
    source
    configuration provider
    Configuration provider
    Log settings updates
    Obtain settings from provider
    Observe settings via provider
    to an object of a specific type.

    Binders are composable: if there's a registered binder for Dictionary<T1, T2>, string and int, then it's possible to bind to Dictionary<string, int>. This is heavily used for collections.

    Overview

    A typical binding process starts with a class binder and proceeds downward by matching fields and properties with scoped node subtrees by names and invoking appropriate binders:

    See all binders descriptions to learn more about this process.

    Error handling

    Binding fails if there's at least one error on any level. Errors may arise from incorrect value formats for primitives, missing values for required fields and properties, mismatches of settings node types or missing binders for requested types.

    In case of failure, a complete list of all errors is presented in resulting exception.

    Related pages

    settings tree
    configuration source
    configuration provider
    binders
    settings node
    Settings nodes
    Settings nodes scoping
    Binders
    Source and packages

    GitHub repository: vostok/configuration.sources.json

    NuGet package: Vostok.Configuration.Sources.Json

    Cement users should reference this module with the following command:

    Related pages

    JSON-based
    source
    file
    string
    parser
    ClusterConfig
    JSON sources
    ClusterConfig source
    If an error occurs and a correct settings instance has already been observed for the requested type at least once, the error is reported in background and does not cause Get method to fail: last seen correct instance is returned from cache instead.

    The second guarantee can be violated by cache overflow events. Read the caching section to find out how to avoid them.

    Observe method never produces OnError or OnCompleted notifications. It only reports successful settings updates. The errors are handled in background and can be logged.

    Related pages

    configuration providers
    sources
    binders
    Configuration provider
    Log errors
    Obtain settings from provider
    Obtain settings from provider
    var settings = new ConfigurationProviderSettings 
    {
        MaxSourceCacheSize = 100_000
    };
    
    var provider = new ConfigurationProvider(settings);
    provider.Get<MySettings>(); // may block and incurs binding costs
    
    provider.Get<MySettings>(); // instantly returns a cached object
    
    // ... the source issues a data update ...
    
    provider.Get<MySettings>(); // instantly returns the old cached object
    
    // ... the cache is automatically updated in background...
    
    provider.Get<MySettings>(); // instantly returns an updated cached object
    Node:
    
    {
      Timeout1: "1 seconds",
      Timeout2: "2 seconds"
      Logging:
      {
        Enabled: "true",
        Levels: ["Info", "Warn"]
      }
    }
    Model:
    
    class AppSettings 
    {
        public TimeSpan Timeout1 { get; }
        public TimeSpan Timeout2 { get; }
        public LoggingSettings Logging { get; }
        
        class LoggingSettings
        {
            public bool Enabled { get; }
            public LogLevel[] Levels { get; }
        }
    }
    Binding process:
    
    Node --> AppSettings via ClassStructBinder
      Node["timeout1"] --> Timeout1 via PrimitiveBinder
      Node["timeout2"] --> Timeout2 via PrimitiveBinder
      Node["logging"] --> Logging via ClassStructBinder
        Node["logging"]["enabled"] --> Enabled via PrimitiveBinder
        Node["logging"]["levels"] --> Levels via ArrayBinder
          Node["logging"]["levels"][0] --> Levels[0] by EnumBinder
          Node["logging"]["levels"][1] --> Levels[1] by EnumBinder
    Install-Package Vostok.Configuration.Sources.Json
    cm ref add vostok.configuration.sources.json <path-to-project>
    // the source contains incorrect data
    
    provider.Get<MySettings>(); // throws an exception
    
    // the source updates with correct data
    
    provider.Get<MySettings>(); // returns settings
    
    // the source updates with incorrect data once again
    
    provider.Get<MySettings>(); // returns settings from cache

    Property

    Values

    Description

    ObjectMergeStyle

    Deep, Shallow

    Type of the merge procedure performed on . Deep is the default style.

    ArrayMergeStyle

    Replace, Concat, Union, PerElement

    Type of the merge procedure performed on . Replace is the default style.

    SettingsNodeMerger is a handy public helper used to merge arbitrary nodes that handles nulls:

    General merging rules

    • If both nodes are null, the result is also null.

    • If one of the nodes is null, the non-null node wins:

      • left + null --> left

      • null + right --> right

    • If nodes are of different types, the right node always wins:

      • value + array --> array

      • array + object --> object

    • If nodes are of same type, special rules apply. They are described in the next sections.

    Merging two value nodes

    Right node always wins: left value + right value --> right value.

    Merging two array nodes

    Replace style (default) always preserves the right array:

    left array + right array --> right array.

    Concat style produces an array containing elements from both arrays. All elements from the left array, then all elements from the second one, preserving order inside arrays.

    [1, 2] + [2, 3] --> [1, 2, 2, 3]

    Union style produces an array containing unique items from both arrays. The order is the same to Concat style.

    [1, 2, 3] + [2, 3, 4] --> [1, 2, 3, 4]

    Per element style produces an array containing items obtained by merging corresponding items (by index) from both arrays. If merged arrays have different children count, the "tail" of the longer array is preserved as-is.

    [1, 2, 6] + [4, 5] --> [4, 5, 6]

    Merging two object nodes

    Deep style (default) produces an object with union of the children from both nodes, then merges children with same names recursively.

    {A:1} + {B:2} --> {A:1, B:2}

    {A: {C:1}, B: {D:2}} + {A: {E:3}, B: {F:4}} --> {A: {C:1, E:3}, B: {D:2, F:4}}

    Shallow style compares children of both nodes by names. If the sets of names match, regardless of order, merges the pairs of matching children recursively. Elsewise, just prefers to return the right node.

    {A:1} + {B:2} --> {B:2}

    {A:1} + {A:2} --> {A:2}

    Related pages

    settings nodes
    combine configuration sources
    Settings nodes
    Combine sources

    Name

    Required if nested in an , optional otherwise.

    Value

    Always returns null. Only can have values.

    Children

    Returns an unordered sequence of child nodes.

    ChildrenCount

    Returns the number of elements in the Children sequence.

    this[name]

    Returns a child node with given name or null if such a node does not exist.

    Equality

    Two object nodes are considered equal if their Children sequences are equivalent (contain equal elements but may present different order) and their names match up to differences in case.

    Representation

    Related pages

    roperty

    Object nodes
    binding

    Description

    Settings nodes
    Settings nodes scoping
    Settings nodes merging
    var result = SettingsNodeMerger.Merge(left, right, SettingsMergeOptions.Default);
    Object(Value("A", "1"), Value("B", "2")) --> { "A": "1", "B": "2" }
    object + value --> value
    object nodes
    array nodes
    object node
    value nodes

    Modules

    Sources

    Description

    This library contains a few basic implementations of configuration sources, such as ObjectSource, FileSource, ConstantSource, CommandLineSource, EnvironmentVariablesSource. It also hosts a set of extensions to manipulate sources (combine, scope, freeze, nest, transform, template) and helpers aiding in creation of custom sources.

    Source and packages

    GitHub repository:

    NuGet package:

    users should reference this module with the following command:

    Related pages

    Abstractions

    Description

    This library contains core interfaces (IConfigurationSource, IConfigurationProvider), intermediate data model (settings nodes) and attributes used to annotate user models, such as RequiredAttribute.

    Source and packages

    GitHub repository:

    NuGet package:

    users should reference this module with the following command:

    Related pages

    Sources.CC

    Description

    This library contains a source that fetches settings from ClusterConfig. It also allows to apply custom parsers in order to support formats other than CC's native one.

    Source and packages

    GitHub repository:

    NuGet package:

    users should reference this module with the following command:

    Related pages

    Sources.Yaml

    Description

    This library contains YAML-based configuration source implementations: file and string. It also exposes a configuration parser for integration with other sources, such as ClusterConfig.

    Source and packages

    GitHub repository:

    NuGet package:

    users should reference this module with the following command:

    Related pages

    Configuration

    Description

    This library contains an implementation of configuration provider, binding, and a couple of custom configuration primitives (such as DataSize and DataRate).

    Source and packages

    GitHub repository:

    NuGet package:

    users should reference this module with the following command:

    Related pages

    Logging

    Description

    This library provides an integration with Vostok logging, allowing to log errors and settings updates.

    Source and packages

    GitHub repository:

    NuGet package:

    users should reference this module with the following command:

    Related pages

    JSON sources

    Location: .

    parses well-formed JSON documents from in-memory strings and supports manual external updates:

    ​ parses JSON files and automatically watches for changes:

    A file that does not exist simply leads to a null , which implies default settings values unless something is .

    vostok/configuration.sources
    Vostok.Configuration.Sources
    Cement
    Configuration sources
    Sources
    Combine sources
    Scope sources
    Create custom sources
    Transform sources
    vostok/configuration.abstractions
    Vostok.Configuration.Abstractions
    Cement
    Settings nodes
    Settings nodes merging
    Settings nodes scoping
    Configuration sources
    Configuration provider
    Basic scenarios
    Make settings secret
    Make settings required
    Install-Package Vostok.Configuration.Sources.ClusterConfig
    vostok/configuration.sources.cc
    Vostok.Configuration.Sources.ClusterConfig
    Cement
    ClusterConfig source
    vostok/configuration.sources.yaml
    Vostok.Configuration.Sources.Yaml
    Cement
    YAML sources
    ClusterConfig source
    vostok/configuration
    Vostok.Configuration
    Cement
    Configuration provider
    Binding nodes to models
    Binders
    Assign sources to types
    Obtain settings from provider
    Observe settings via provider
    vostok/configuration.logging
    Vostok.Configuration.Logging
    Cement
    Log errors
    Log settings updates
    Related pages
    var source = new JsonStringSource("<json content>");
    
    ​source.Push("<json content>") // update with new content
    var source = new JsonFileSource("settings/config.json");
    Sources.Json module
    JsonStringSource
    JsonFileSource
    node
    explicitly required
    Configuration sources
    Install-Package Vostok.Configuration.Sources
    cm ref add vostok.configuration.sources <path-to-project>
    Install-Package Vostok.Configuration.Abstractions
    cm ref add vostok.configuration.abstractions <path-to-project>
    cm ref add vostok.configuration.sources.cc <path-to-project>
    Install-Package Vostok.Configuration.Sources.Yaml
    cm ref add vostok.configuration.sources.yaml <path-to-project>
    Install-Package Vostok.Configuration
    cm ref add vostok.configuration <path-to-project>
    Install-Package Vostok.Configuration.Logging
    cm ref add vostok.configuration.logging <path-to-project>

    Sources.Vault

    Description

    This library contains a implementation based on secrets.

    Source and packages

    Microsoft

    Description

    This library provides a one-way integration with Microsoft , allowing to inject Vostok as MS configuration providers.

    Source and packages

    Sources.Xml

    Description

    This library contains configuration implementations: and . It also exposes a configuration for integration with other sources, such as .

    Sources

    GitHub repository: vostok/configuration.sources.vault

    NuGet package: Vostok.Configuration.Sources.Vault

    Cement users should reference this module with the following command:

    Related pages

    source
    HashiCorp Vault
    Vault source
    Make settings secret

    GitHub repository: vostok/configuration.microsoft

    NuGet package: Vostok.Configuration.Microsoft

    Cement users should reference this module with the following command:

    configuration extensions
    sources
    Install-Package Vostok.Configuration.Microsoft
    cm ref add vostok.configuration.microsoft <path-to-project>
    Source and packages

    GitHub repository: vostok/configuration.sources.xml

    NuGet package: Vostok.Configuration.Sources.Xml

    Cement users should reference this module with the following command:

    Related pages

    XML-based
    source
    file
    string
    parser
    ClusterConfig
    XML sources
    ClusterConfig source
    Install-Package Vostok.Configuration.Sources.Vault
    cm ref add vostok.configuration.sources.vault <path-to-project>
    Install-Package Vostok.Configuration.Sources.Xml
    cm ref add vostok.configuration.sources.xml <path-to-project>

    Object source

    Location: main sources module.

    ObjectSource converts an arbitrary object (including anonymous types) to a settings node:

    var source = new ObjectSource(new {A = 1, B = 2, C = new[] {1, 2, 3});

    Conversion process obeys the following priority list:

    • Objects with overridden ToString are converted to value nodes;

    • Dictionaries with primitive key types are converted to ;

      • Conversion is performed recursively for keys and values;

    • Objects that implement IEnumerable are converted to ;

      • Conversion is performed recursively for sequence elements;

    • Everything else is converted to ;

      • Conversion is performed recursively for public instance fields and properties;

    Like , object source does not produce any updates by itself; conversion exceptions are exposed via (null, error) notifications.

    Related pages

    Constant sources

    Location: main sources module.

    ConstantSource

    A constant source returns a preconfigured settings node and never issues any updates.

    var source = new ConstantSource(new ValueNode("value"));

    A lazy constant source does the same but defers the node acquisition until first subscription:

    Errors in the provided delegate are propagated via (null, error) notification. It's guaranteed to execute not more than once.

    Practical use

    Constant sources are handy for unit testing and may serve as base classes for .

    Related pages

    object nodes
    array nodes
    object nodes
    constant sources
    Configuration sources

    Binders

    Binders convert settings nodes to objects. There are built-in binders for primitives, collections, classes and structs. One can also extend the library by implementing a custom binder.

    Related pages

    Binding nodes to modelsSettings nodes

    Vault source

    This source is not documented yet. Come back for updates later.

    ClusterConfig source

    This source is not documented yet. Come back for updates later.

    Basic scenarios

    LazyConstantSource
    custom sources
    Configuration sources
    var source = new LazyConstantSource(() => new ValueNode("value"));

    Primitives

    Primitive types are parsed from string values in value nodes.

    Supported types

    Type

    Allowed format examples

    string

    Literally any string.

    and are custom new types. They also provide factory extensions and operators:

    Parse method convention

    There's also support for types that implement Parse or TryParse method with standard signature:

    This allows to use arbitrary types with string representation without resorting to .

    Node requirements

    or a container (array/object) node with a single value node child.

    Null node handling

    Default value for the type is used unless .

    Explicitly specified null string value has the same effect.

    Incorrect format handling

    Parsing errors arising from incorrect value formats lead to binder failure and result in exceptions, even for optional members (see for more context).

    Related pages

    XML sources

    Location: Sources.Xml module.

    XmlStringSource parses well-formed XML documents from in-memory strings and supports manual external updates:

    var source = new XmlStringSource("<xml content>")
    
    source.Push("<xml content>") // update with new content

    XmlFileSource parses XML files and automatically watches for changes:

    var source = new XmlFileSource("settings/config.xml");

    A file that does not exist simply leads to a null node, which implies default settings values unless something is explicitly required.

    Related pages

    YAML sources

    Location: Sources.Yaml module.

    YamlStringSource parses well-formed YAML documents from in-memory strings and supports manual external updates:

    var source = new YamlStringSource("<yaml content>")
    
    source.Push("<yaml content>") // update with new content

    YamlFileSource parses YAML files and automatically watches for changes:

    var source = new YamlFileSource("settings/config.yml");

    A file that does not exist simply leads to a null node, which implies default settings values unless something is explicitly required.

    Related pages

    Collections

    Supported types

    Arrays and lists:

    • T[]

    • List<T>

    • IEnumerable<T> (backed by T[])

    • IReadOnlyList<T>(backed by T[])

    • IReadOnlyCollection<T>(backed by T[])

    • ICollection<T> (backed by List<T>)

    • IList<T>(backed by List<T>)

    Dictionaries:

    • Dictionary<TKey, TValue>

    • IDictionary<TKey, TValue> (backed by Dictionary)

    • IReadOnlyDictionary<TKey, TValue> (backed by Dictionary

    Sets:

    • HashSet<T>

    • ISet<T> (backed by HashSet)

    Collections can have and other collections as elements.

    Binder requirements

    Collections require binders defined for element types (both keys and values in case of dictionaries).

    Node requirements

    or node. Empty nodes are converted to empty collections.

    Null node handling

    An empty collection is returned unless .

    Nulls are not valid as dictionary keys.

    Error handling

    Any element binding failure results in complete collection binding failure.

    Comparer customization

    A custom element comparer (such as StringComparer.OrdinalIgnoreCase for case-insensitive dictionary keys) can be achieved by wrapping the collection in a custom type and utilizing the .

    Related pages

    Constructor injection

    A type that has exactly one constructor with a single argument of a type that can be bound can also be bound with argument injection. This is especially useful for customization:

    This technique can also be used for additional initialization or settings data preprocessing:

    Related pages

    Configuration sources
    Configuration sources
    )
    classes, structs
    Array
    object
    explictly required
    constructor binding convention
    Binding nodes to models
    Constructor injection

    Advanced scenarios

    char

    Literally any single character.

    bool

    true, True, TRUE

    byte, sbyte

    Anything built-in TryParse method can grok.

    short, ushort

    Anything built-in TryParse method can grok.

    int, uint

    Anything built-in TryParse method can grok.

    long, ulong

    Anything built-in TryParse method can grok.

    float, double, decimal

    1.23, 1,23, 5,12e2

    Guid

    Anything built-in TryParse method can grok.

    Uri

    http://example.com, example.com/some, /part/of/path

    TimeSpan

    00:12:34, 2 seconds, 500 ms, 1.5 days, 10s, 0.5 minutes

    DateTime(Offset)

    2018-03-14 15:09:26.535, 20050809T181142+0330

    IPAddress

    127.0.0.1, 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d

    IPEndPoint

    192.168.1.10:80

    DataSize

    453453 (just bytes), 1 kb, 24.3 megabytes, 500 TB

    DataRate

    500 (just bytes/sec), 200 kilobytes/second, 5 GB/sec, 80 mb/s

    Encoding

    utf-8, us-ascii

    Enum types

    Anything built-in TryParse method can grok.

    Nullable structs

    A valid value or null.

    DataSize
    DataRate
    implementation of custom binders
    Value node
    explictly required
    classes and structs
    Binding nodes to models
    class PerServiceSettings : Dictionary<string, string>
    {
        public PerServiceSettings(Dictionary<string, string> dictionary)
            : base(dictionary, StringComparer.OrdinalIgnoreCase)
        {
        }
    }
    class PerServiceSettings
    {
        public PerServiceSettings(Dictionary<string, string> data)
        {
            Index = data;
            
            InvertedIndex = Index.ToDictionary(
                pair => pair.Value,
                pair => pair.Key
            );
        }
    
        public Dictionary<string, string> Index { get; }
        
        public Dictionary<string, string> InvertedIndex { get; }
    }
    collection
    Collections

    Make settings secret

    Requires: abstractions module.

    By default settings are subject to logging, which is generally not appropriate for secrets. This can be fixed either by disabling logging entirely on the configuration provider or by annotating fields and properties of the model with [Secret] attribute:

    class MySettings 
    {
        [Secret]
        public MySecrets Secrets { get; } = new MySecrets();
        
        public TimeSpan Timeout { get; } = TimeSpan.FromSeconds(30);
        
        [Secret]
        public string EncryptionKey { get; }
    }
    
    class MySecrets 
    {
        public string ApiKey { get; }
        public string Login { get; }
        public string Password { get; }
    }

    Related pages

    Log settings updatesLog errors

    Assign sources to types

    Requires: main module.

    When using configuration providers, Get and Observe method overloads without parameters require an explicit association of the model type with a configuration source.

    // This simply won't work (no source defined):
    
    provider.Get<MySettings>();
    provider.Observe<MySettings>();
    // This will work as expected:
    
    provider.SetupSourceFor<MySettings>(new JsonFileSource("settings.json"));
    
    provider.Get<MySettings>();
    provider.Observe<MySettings>();

    A source can only be assigned to a type before any Get or Observe calls are made for that type:

    provider.SetupSourceFor<MySettings>(source1); // succeeds
    
    provider.SetupSourceFor<MySettings>(source2); // succeeds, overwrites
    
    provider.Get<MySettings>();
    
    provider.SetupSourceFor<MySettings>(source3); // fails

    Log settings updates

    Requires: logging module.

    See logging documentation for the log interface required here.

    var providerSettings = new ConfigurationProviderSettings();
    
    providerSettings = providerSettings.WithSettingsLogging(log);
    
    var provider = new ConfigurationProvider(providerSettings);

    Secret settings should be marked with an attribute to avoid being logged.

    DataSize size = 50.Megabytes();
    DataRate rate = size / 2.Seconds();
    class/struct T 
    {
        public static T Parse(string input) { ... }
        
        public static bool TryParse(string input, out T result) { ... }
    }

    Use name aliases

    Requires: abstractions module.

    Name aliases provide alternative keys to look for in settings nodes when performing binding.

    They can be applied to fields and properties with a special attribute:

    class MySettings 
    {
        [Alias("LegacyTimeout")]
        public TimeSpan Timeout { get; }
    }
    
    // The property can be initialized from either "timeout" or "legacytimeout" key

    It's allowed to assign multiple aliases to a single member:

    [Alias("alias1")]
    [Alias("alias2")]
    public TimeSpan Timeout { get; }

    Aliases do not handle ambiguity. If the source returns data with more than one of the lookup keys assigned to a field or property, binding fails with an error.

    Ambiguous data examples:
    { "timeout": "...", "alias1": "..." }
    { "alias1": "...", "alias2": "..." }

    Related pages

    Classes and structs

    How it works

    The binder starts off by creating an instance of the bound type with default values of public fields and properties (the same way as Activator.CreateInstance does) and then recursively binds the value of every field and property to a corresponding subsection of the settings tree. See binding nodes to models page for a step-by-step illustration of this process.

    Fields and properties don't have to be mutable: readonly fields and properties with private setters (or even without one) are fine.

    Following members are ignored: indexers, constants, static and non-public fields and properties, computed properties without backing fields.

    Nested classes and are supported without restrictions.

    Settings tree navigation

    When binding a field or property value, the settings tree is to the member name. This imposes a requirement to synchronize naming in configuration data provided by and models in the application code. In order to bind a model property named Timeout from a section of JSON file, this section must contain a property with the same name (barring case).

    allow to decouple property names from configuration data names.

    Required and optional members

    By default all fields and properties are treated as optional: they are just left with default values if no data could be found in the settings tree (default values may come from field and property initializers). It's possible to though. Required members with no data in the settings tree cause the binding process to fail and produce an exception.

    Model requirements

    Model classes must either have a parameterless constructor, a constructor with a single parameter, or have an OmitConstructorsAttribute (which allows , therefore, no default parameters are present).

    Binder requirements

    Binders are required for all public fields and properties.

    Node requirements

    Only are supported.

    Related pages

    Binding nodes to models
    Classes and structs
    collections
    scoped
    sources
    Name aliases
    make members required
    creating an uninitialized object
    object nodes
    Binding nodes to models
    Make settings required
    Make settings secret
    Use name aliases
    Apply custom binders

    Use value substitutions

    Requires: sources module.

    Value substitution feature allows to replace placeholders in settings data. It's based on the transformation extensions and uses #{} syntax for placeholders.

    var originalSource = new JsonFileSource("settings.json");
    
    var substitutions = new []
    {
        new Substitution("param1", "value1"),
        new Substitution("param2", () => "value2")
    };
    
    var templatedSource = originalSource.Substitute(substitutions);
    Data in originalSource:
    {
        "param1": "#{param1}",
        "param2": "#{param2}",
        "param3": "value3",
        "param4": "#{param4}"
    }

    Related pages

    SourcesTransform sources

    Nest sources

    Requires: sources module.

    This operation allows to embed source's data into a nested object section:

    var source = new ObjectSource(new {A = 1, B = 2});
    
    var nestedSource = source.Nest("obj1", "obj2");
    Data in nestedSource:
    
    {
        "obj1":
        {
            "obj2":
            {
                "A": "1",
                "B": "2"
            }
        }
    }

    Related pages

    Configuration sourcesScope sources
    Model                             JSON data source
    
    class Settings                    {
    {
        TimeSpan Timeout { get; } -------> "timeout": "2 seconds",
        
        int Parallelism { get; }  -------> "parallelism": 32
    }                                 }
    Data in templatedSource:
    {
        "param1": "value1",
        "param2": "value2",
        "param3": "value3",
        "param4": "#{param4}"
    }

    Use shared provider instance

    Requires: main module.

    Configuration provider class has a property named Default that serves as a global static source of IConfigurationProvider instance.

    It's intended use case is self-sufficient configuration in libraries: library authors may not want to force their users to provide an IConfigurationProvider instance each time they're using library classes. Instead, these classes could just obtain a log from the shared property:

    ConfigurationProvider.Default.SetupSourceFor<MyLibrarySettings>(...);
    
    var settings = ConfigurationProvider.Default.Get<MyLibrarySettings>();

    By default this property returns a singleton provider with default settings. It can be configured explicitly in the application:

    var providerSettings = new ConfigurationProviderSettings { ... };
    var provider = new ConfigurationProvider(providerSettings);
    
    // Will return false if already explicitly configured:
    ConfigurationProvider.TrySetDefault(provider);

    Related pages

    Configuration provider

    Use dynamic interfaces

    Requires: main module.

    The basic recommended way to get most up-to-date settings in the presence of background updates is to use provider's Get method (see the relevant scenario) on each access attempt. However, it's also possible to obtain an inherently dynamic settings object whose properties are updated under the hood. This requires to use an interface as the settings model:

    interface IMySettings
    {
        string Option1 { get; }
        string Option2 { get; }
        ISubConfig Section { get; }
    }
    
    // Get once, use as a singleton:
    var hotSettings = provider.CreateHot<IMySettings>(); 
    
    // Properties of the 'hotSettings' object are mutable. 
    // They will automatically return most up-to-date values.

    Note that in order to get a guaranteed consistent view of the settings without "tearing" (observing a mix of values from before and after update due to a race condition) when using a nested object (section), it's recommended to access a snapshot of this nested object saved in a variable:

    var section = hotSettings.Section;
    
    // access a consistent view of 'section` properties

    Related pages

    Obtain settings from providerAssign sources to types

    Combine sources

    Requires: main sources module.

    Multiple configuration sources can be combined into a single composite source whose data is produced by merging the settings trees provided by original sources.

    var source1 = new JsonStringSource(...);
    var source2 = new YamlStringSource(...);
    var source3 = new XmlStringSource(...);
    
    var combined = source1.CombineWith(source2, source3);
    
    // combined == Merge(source1, source2, source3)
    
    // combined.Data == Merge(Merge(source1.data, source2.data), source3.data)

    Order of the sources is important: settings from sources that come later in the list have greater priority, hence the rightmost source should be the most specific/significant. In other words, merging is performed in a left-to-right fashion.

    Updates are pushed to subscribers each time one of the component sources generates new settings.

    Related pages

    Create custom sources

    Requires: .

    offers a couple of base classes to assist in implementing custom .

    For in-memory sources or sources that can update an in-memory one from a remote API.

    Apply custom validators

    Requires: , (constraints).

    Validation feature allows to associate a custom user-made validator with a settings type. Validation occurs during and results in binding errors for incorrect settings.

    Constraints

    There are also built-in validation constraints you can use to create a custom validator. Just inherit your validator class from ConstraintsValidator and override a method returning constraints to be checked:

    Apply custom binders

    Requires: .

    This extension point covers scenarios where are not sufficient to handle required types.

    Custom binders should be used as a last resort when and are not powerful enough.

    Creating a binder for a type is just a matter of implementing an interface:

    Custom binders can be applied to specific fields and properties or whole types:

    Freeze sources

    Requires: .

    This operation allows to effectively disable data updates on a source:

    Related pages

    Print contents of a source

    Requires: .

    Related pages

    Scope sources

    Requires: .

    A source can be scoped just like . Resulting source's data is exactly base source's data scoped to given path:

    Related pages

    Settings nodes merging
    Configuration sources
    Here's a list of all currently implemented constraint types:
    • NotNullConstraint for arbitrary reference types;

    • NotNullOrEmptyConstraint for strings;

    • NotNullOrWhitespaceConstraint for strings;

    • RangeConstraint, LessConstraint, LessOrEqualConstraint, GreaterConstraint and GreaterOrEqualConstraint for any types that implement IComparable;

    • UniqueConstraint to check that a set of field/properties only contains unique values;

    Related pages

    abstractions module
    main module
    binding
    Obtain settings from provider
    Binding nodes to models
    Related pages
    class MyComplexTypeBinder : ISettingsBinder<MyComplexType>
    {
        public MyComplexType Bind([CanBeNull] ISettingsNode node)
        {
            ...
        }
    }
    class MySettings 
    {
        [BindBy(typeof(MyComplexTypeBinder))]
        public MyComplexType ComplexProperty { get; }
    }
    abstractions module
    existing binders
    constructor injection
    parsing conventions
    Binding nodes to models
    Binders
    var source = new JsonFileSource("settings.json");
    
    var frozenSource = source.Freeze();
    
    // frozenSource will not issue updates after first successful one
    sources module
    Configuration sources
    var source = new JsonFileSource("settings.json");
    
    ISettingsNode contents = source.Get();
    
    Console.Out.WriteLine(contents);
    main module
    Settings nodes
    Print settings
    var baseSource = new JsonFileSource("settings.json");
    var scopedSource = baseSource.ScopeTo("secrets");
    var scopedTooFarSource = baseSource.ScopeTo("timeouts", "unknown-section");
    Data in baseSource:
    
    {
        "Timeouts":
        {
            "DbTimeout": "20 seconds"
        },
        "Secrets":
        {
            "ApiKey": "xxxx-xxxx-xxxx"
        } 
    }
    main sources module
    settings nodes can
    Settings nodes scoping
    Configuration sources
    FileSource

    For sources based on local files. Includes automatic reload on file updates.

    Related pages

    sources module
    Sources module
    configuration sources
    ManualFeedSource
    Configuration sources
    Constant sources
    Combine sources
    Scope sources
    Nest sources
    Freeze sources
    [ValidateBy(typeof(MySettingsValidator))]
    class MySettings
    {
        int CacheCapacity { get; }
    }
    class MySettingsValidator: ISettingsValidator<MySettings>
    {
        // Returns validation errors. Empty enumerable == success.
        public IEnumerable<string> Validate(MySettings settings)
        {
            if (settings.CacheCapacity <= 0)
                yield return "Cache capacity must be positive.";
        }
    }
    [ValidateBy(typeof(TestConfigValidator))]
    class TestConfig
    {
        public int Number;
        public string String;
    }
    class TestConfigValidator : ConstraintsValidator<TestConfig>
    {
        protected override IEnumerable<Constraint<TestConfig>> GetConstraints()
        {
            yield return new NotNullOrWhitespaceConstraint<TestConfig>(settings => settings.String);
            yield return new RangeConstraint<TestConfig, int>(settings => settings.Number, 2, 10);
        }
    }
    [BindBy(typeof(MyComplexTypeBinder))]
    class MyComplexType
    {
    }
    Data in scopedSource:
    
    {
        "Secrets":
        {
            "ApiKey": "xxxx-xxxx-xxxx"
        }
    }
    Data in scopedTooFarSource:
    
    {
    }
    class MySource : ManualFeedSource<string>
    {
        public MySource()
            : base(Parse) { }
    
        private static ISettingsNode Parse(string input) { ... }
    }
    
    var source = new MySource();
    
    source.Push("input1");
    source.Push("input2");
    
    // Push method can be used by an internal periodical/event-based update routine.
    class MyFileSource : FileSource
    {
        public MyFileSource(string filePath)
            : base(new FileSourceSettings(filePath), Parse) { }
            
        private static ISettingsNode Parse(string input) { ... }
    }

    Apply source data to existing object

    Requires: abstractions module, main module.

    var settings = new MySettings();
    
    var source = GetSource(); // IConfigurationSource
    
    source.ApplyTo(settings); // doesn't overwrite fields and properties not present in the source