Table of Contents

Bodu.Extensions.Configuration.Text

Bodu.Extensions.Configuration.Text

Bodu.Extensions.Configuration.Text is the bridge between Bodu.Text.Configuration and Microsoft.Extensions.Configuration, and one half of the Configuration topic. It exposes a single conventional entry point - IConfigurationBuilder.AddTextConfigurationFile(...) - that adds a Bodu Text Configuration file (or stream, or pre-parsed document) as a configuration source alongside JSON, INI, XML, and environment variables.

The overload set deliberately mirrors Microsoft.Extensions.Configuration.Json's AddJsonFile / AddJsonStream shape, so consumers familiar with the JSON provider can swap in this provider without learning a new API. A second, read-only TOML bridge (AddTomlFile / AddTomlStream) layers Bodu.Text.Toml through the same pipeline, and a third, read-only Bencode bridge (AddBencodeFile / AddBencodeStream) does the same for Bodu.Text.Bencode. Once added, keys are exposed in the canonical colon-delimited form that IConfiguration consumes:

configuration["logging:level:default"]   // "Warning"
configuration.GetSection("service")      // a child section that binds to your POCO

Core mental model

Configuration flow - builder to provider to IConfiguration to IOptions

The provider is a thin host around Bodu.Text.Configuration. The builder creates a TextConfigurationSource (or its stream-only sibling TextStreamConfigurationSource); the source's Build method instantiates a TextConfigurationProvider; the provider loads the file, parses it with the supplied ConfigurationParseOptions, resolves it for the source's TargetPath using the supplied ConfigurationResolveOptions, and copies the resolved view into IConfiguration.Data as colon-delimited keys. The DI extensions (AddConfigurationOptions<T>) bind a named section to an IOptions<T> instance.

IConfigurationBuilder
  ▶ AddTextConfiguration{File|Stream|Document}(path | stream | document, …)
  ▶ TextConfigurationSource ▶ Build()
  ▶ TextConfigurationProvider.Load()
  ▶ Parse + Resolve via Bodu.Text.Configuration
  ▶ IConfiguration["key:subkey"]
  ▶ services.AddConfigurationOptions<TOptions>(config, "section")
  ▶ IOptions<TOptions>

The shape of the library

Everything lives in the Bodu.Extensions.Configuration.Text namespace.

Builder extensions

The primary entry point. Mirrors AddJsonFile / AddJsonStream exactly so call sites stay familiar.

Type Purpose
TextConfigurationExtensions Static class. The AddTextConfiguration* overload family: file path, file path + file provider, configure callback, conventional probe (.boduconfig → bodu.config), stream, and pre-parsed IniDocumentBase.
TomlConfigurationExtensions Static class. The read-only TOML bridge: AddTomlFile(path, optional) and AddTomlStream(stream). Read-once, read-only, no reload-on-change.
BencodeConfigurationExtensions Static class. The read-only Bencode bridge: AddBencodeFile(path, optional) and AddBencodeStream(stream). Read-once, read-only, dictionary-rooted documents only.

Sources and providers

The plumbing - typically not constructed directly. Use the builder extensions instead.

Type Purpose
TextConfigurationSource FileConfigurationSource subclass. Inherits Path, Optional, ReloadOnChange, FileProvider; adds TargetPath, ParseOptions, ResolveOptions.
TextConfigurationProvider FileConfigurationProvider subclass. Reads the file via the standard MEC pipeline and projects the resolved view into Data.
TextStreamConfigurationSource StreamConfigurationSource subclass. One-shot: no reload-on-change.
TextStreamConfigurationProvider The matching stream provider.
Internal loader (not public) Parses + resolves a stream into a flat key/value dictionary; reused by both Text* providers.
TomlConfigurationSource Implements IConfigurationSource directly (not a FileConfigurationSource). Carries Path, Optional, Stream. Read-once - no ReloadOnChange.
TomlConfigurationProvider The matching TOML provider. Flattens the TOML table hierarchy into colon-delimited keys.
BencodeConfigurationSource The Bencode counterpart - implements IConfigurationSource directly with the same Path, Optional, Stream shape. Read-once.
BencodeConfigurationProvider The matching Bencode provider. Requires a dictionary-rooted document and flattens nested dictionaries into colon-delimited keys.

Options binding

Thin shims over services.Configure<TOptions>(...) - there for discoverability.

Type Purpose
ConfigurationOptionsExtensions Static class. AddConfigurationOptions<TOptions>(services, configuration, sectionName) and the section overload AddConfigurationOptions<TOptions>(services, IConfigurationSection).

Common scenarios

Scenario Reach for
Add a .boduconfig file to the builder builder.AddTextConfigurationFile(".boduconfig")
Conventional probe - try .boduconfig then bodu.config builder.AddTextConfiguration() (no-arg)
Anchor glob resolution to a specific source path builder.AddTextConfigurationFile("appsettings.bodu", targetPath: "src/Foo.cs")
Optional file - do not throw if missing builder.AddTextConfigurationFile("appsettings.bodu", optional: true)
Reload-on-change builder.AddTextConfigurationFile("appsettings.bodu", reloadOnChange: true)
Use a specific IFileProvider builder.AddTextConfigurationFile(physicalFileProvider, "appsettings.bodu")
Read from a stream (test fixtures, embedded resources) builder.AddTextConfigurationStream(stream)
Wire up everything via a configure callback builder.AddTextConfigurationFile(src => { src.Path = …; src.TargetPath = …; src.ReloadOnChange = true; })
Bind a section to an options class services.AddConfigurationOptions<MyOptions>(configuration, "service")
Pre-parsed document (already loaded elsewhere) builder.AddTextConfigurationDocument(document, targetPath: …)
Add a read-only TOML file builder.AddTomlFile("appsettings.toml", optional: true)
Add a read-only TOML stream builder.AddTomlStream(stream)
Add a read-only Bencode file builder.AddBencodeFile("appsettings.bencode", optional: true)
Add a read-only Bencode stream builder.AddBencodeStream(stream)

Conventional file probe

The no-argument overload probes the builder's base path for two conventional names:

Order File name
1 .boduconfig
2 bodu.config

The first file found is added as a configuration source. When neither is present and optional is true, the call is a no-op; when optional is false, the source surfaces as missing per the standard FileConfigurationProvider contract.

Reload-on-change

TextConfigurationSource inherits the ReloadOnChange property from FileConfigurationSource. When true, the provider attaches a file watcher through the configured IFileProvider; any change to the underlying file triggers a reparse + reload, and any reload tokens issued through IConfiguration fire.

TextStreamConfigurationSource does not support reload-on-change - it parses the stream once when Build is called, and the stream lifetime ends with that parse. For dynamic stream-backed inputs, rebuild the configuration.

The TOML and Bencode bridges (AddTomlFile / AddTomlStream, AddBencodeFile / AddBencodeStream) are read-once and read-only by design - they attach no file watcher even for the file overloads, so there is no reloadOnChange parameter on any of the four methods.

How keys are projected

The provider hands its parsed document to the resolver and copies the resulting ConfigurationView into the inherited Data dictionary. Under the default Default key options, the DotToColon mapping rewrites dotted file keys to the colon delimiter IConfiguration expects - logging.level.default becomes logging:level:default. Keys are stored under StringComparer.OrdinalIgnoreCase, matching the JSON and INI providers, so configuration["Logging:Level"] and configuration["logging:level"] resolve to the same value.

The projection is intentionally lossy relative to the richer document model. Comments, source locations (line/column/path), and duplicate-section provenance are preserved on the parsed ConfigurationDocument but discarded during the flatten. A literal colon inside a key segment cannot survive - once flattened, the colon is the hierarchy delimiter. Consumers who need any of that metadata should depend on ConfigurationDocument directly rather than going through the bridge.

Where to go next