Bodu.Extensions.Configuration.Text - Core concepts
This page is the vocabulary the rest of the documentation assumes. Read it once before the getting-started samples, and refer back whenever a term feels imprecise.
Part of the Configuration topic.
For the high-level shape of the library, start with the introduction.
Source vs provider vs loader
The library follows the three-part contract every Microsoft.Extensions.Configuration provider uses:
| Role | Type | Responsibility |
|---|---|---|
| Source | TextConfigurationSource, TextStreamConfigurationSource | Holds the configuration of "what to load and how" - the path, target path, parse and resolve options, reload behaviour. Implements Build(IConfigurationBuilder). |
| Provider | TextConfigurationProvider, TextStreamConfigurationProvider | Performs the actual load. Subclasses FileConfigurationProvider / StreamConfigurationProvider; inherits change-token plumbing. Populates the inherited Data dictionary. |
| Loader | Internal (not public) | Internal helper that parses a stream into a ConfigurationDocument, resolves it, and flattens the view into Dictionary<string, string?> for the provider. |
Both providers share the same loader, so behaviour stays in lockstep across file and stream backings.
File vs stream source
The two source types correspond to the two file shapes Microsoft ships:
| Source | Backing | Reload-on-change |
|---|---|---|
| TextConfigurationSource | Path resolved through an IFileProvider |
Yes |
| TextStreamConfigurationSource | Arbitrary System.IO.Stream |
No |
File sources are the common case - they layer naturally with appsettings.json, support hot reload, and accept the
same path-based options every provider does. Stream sources are useful for tests, embedded resources, or in-memory
fixtures where the configuration data does not live on disk.
A third source, TomlConfigurationSource, backs the read-only TOML bridge
(AddTomlFile / AddTomlStream). It carries Path, Optional, and Stream but no TargetPath, ParseOptions,
ResolveOptions, or ReloadOnChange - TOML has no glob-anchored resolution layer and the bridge is read-once. A
fourth, BencodeConfigurationSource, backs the read-only Bencode bridge
(AddBencodeFile / AddBencodeStream) with the same three-property shape; its documents must be dictionary-rooted.
Target path
TargetPath is the value the source hands to ConfigurationDocument.Resolve(targetPath). It anchors glob
matching for sections whose headers contain path separators.
builder.AddTextConfigurationFile(
"team.boduconfig",
targetPath: "src/MyApp/Program.cs");
When the file says
[src/**/*.cs]
logging.level.default = Warning
the [src/**] pattern matches src/MyApp/Program.cs, so the resolved view picks up that section's properties on top
of any earlier matches. When TargetPath is null, only unanchored patterns and preamble values contribute.
TargetPath is per source - different sources in the same builder can have different anchors. If your application
needs configuration evaluated for several paths in parallel, add several sources with the same Path but different
TargetPath values.
Parse and resolve option propagation
A source carries two optional bags:
| Source property | Drives |
|---|---|
ParseOptions |
The ConfigurationParseOptions passed to the reader. Controls inline-comment mode, duplicate handling, diagnostic mode, length limits. |
ResolveOptions |
The ConfigurationResolveOptions passed to the resolver. Controls PathRoot, MissingPathRootMode, UnsetValueMode, PathComparison. |
Both default to null, in which case the library uses
Bodu and
Bodu. Set them per-source when one file in a builder
needs different semantics - for example, an EditorConfig-strict file alongside a Bodu-permissive one.
builder.AddTextConfigurationFile(src =>
{
src.Path = ".editorconfig";
src.ParseOptions = ConfigurationParseOptions.EditorConfigCompatible;
src.ResolveOptions = ConfigurationResolveOptions.EditorConfigCompatible;
src.TargetPath = "src/Foo.cs";
});
Conventional file probe
The no-argument overload of AddTextConfiguration is a convenience helper for the common "drop a file in the project
root" pattern:
builder.AddTextConfiguration(optional: true, reloadOnChange: true);
The probe runs against the builder's default file provider and looks for two file names in order:
.boduconfig- the dotfile form, common in version-control-friendly repos.bodu.config- the plain form, common on Windows where dotfiles need explicit attribute toggles.
The first file found is added; if neither is present and optional is true, the helper returns the builder
unchanged (it registers a .boduconfig source marked optional so the slot still exists). When neither is present and
optional is false, the helper throws FileNotFoundException. The probe consults
IFileProvider.GetFileInfo(name).Exists and runs once, at registration time.
Note
A default PhysicalFileProvider filters dot-prefixed files via its ExclusionFilters (Sensitive by default), so
.boduconfig will not resolve unless you register a PhysicalFileProvider constructed with ExclusionFilters.None.
The plain bodu.config fallback is resolvable without that change.
Reload-on-change
FileConfigurationSource.ReloadOnChange is inherited as-is. When true, the provider:
- Attaches a file watcher via the source's
IFileProvider. - On a change notification, reparses the file and rebuilds the resolved view.
- Triggers the standard
IConfigurationreload tokens, so callers usingIOptionsMonitor<TOptions>re-bind to the new values automatically.
Reload is not atomic with respect to multiple providers - if your builder has three file sources and two change at once, the providers reload independently, in the order their watchers fire. This matches the MEC contract and is not specific to Bodu.
The stream source does not support reload. The stream is parsed once when Build is called; the stream's lifetime
ends with that parse. If you need dynamic stream-backed inputs, rebuild the configuration.
File provider precedence
AddTextConfigurationFile resolves the IFileProvider in the standard MEC order:
- If a provider is supplied directly to the overload, use it.
- Otherwise, if the source's
FileProvideris set, use that. - Otherwise, defer to the builder's default file provider - typically a
PhysicalFileProviderrooted atDirectory.GetCurrentDirectory().
Tests typically supply a PhysicalFileProvider rooted at a temp directory; production code typically relies on the
builder default.
Options binding
ConfigurationOptionsExtensions wraps the standard
services.Configure<TOptions>(...) shape:
services.AddConfigurationOptions<ServiceOptions>(configuration, "service");
// equivalent to:
services.Configure<ServiceOptions>(configuration.GetSection("service"));
The wrapper exists for discoverability - call sites that reach for an AddTextConfiguration* API by IntelliSense
find an options helper with the same prefix. The shape is identical to the MEC version; callers who already use
Configure<T> are not penalised, and callers who switch to AddConfigurationOptions are not locked in.
The two overloads:
| Overload | Use when |
|---|---|
AddConfigurationOptions<T>(services, configuration, sectionName) |
Section is identified by colon-delimited name in an IConfiguration root. |
AddConfigurationOptions<T>(services, section) |
Section is already projected via configuration.GetSection(...). |
Both throw ArgumentNullException for null services / configuration / section; the name-based overload throws
ArgumentException for an empty or whitespace section name.
Colon-delimited key model
IConfiguration keys are colon-delimited by convention - service:name, logging:level:default. Bodu's reader
projects raw keys to the same shape under the default DotToColon mapping, so a file written as
service.name = Bodu
service.port = 8080
logging.level.default = Information
surfaces as configuration["service:name"], configuration["service:port"],
configuration["logging:level:default"]. The mapping is the Mapping property on
ConfigurationKeyOptions; switching it to
Identity emits keys unchanged - useful when the file is also
consumed by tools that interpret dots as path separators (e.g. AppSettings patches).
The projection is lossy by design. The flatten step in
the providers' shared internal loader keeps only the resolved key/value pairs; comments,
source locations, and duplicate-section provenance present on the parsed
ConfigurationDocument do not survive. Keys are stored case-insensitively
(StringComparer.OrdinalIgnoreCase), and a literal colon inside a key segment is interpreted as a hierarchy boundary
once flattened. Reach for ConfigurationDocument directly when you need any of that
discarded metadata.
Pre-parsed document source
AddTextConfigurationDocument takes an
already-parsed IniDocumentBase - such as a ConfigurationDocument -
resolves it once against targetPath, and adds the flattened pairs via the in-memory provider. It is a one-shot
snapshot: the document is captured by value when the method is called, so later edits to the document (or its backing
file) are not reflected. Use it to share a single parse across several builders, or to feed a document built or mutated
in code. There is no reload-on-change for this overload.
Where to go next
- Getting started - install + runnable minimal samples.
- Bodu.Text.Configuration - the underlying parser, resolver, and view.
- Bodu.Extensions.Configuration.Text API reference - full type-by-type docs.
- Introduction - the high-level shape of the library.
- Configuration topic - this package and its sibling Bodu.Text.Configuration; the topic concepts page collects the shared vocabulary.