ConfigBoundNET 0.1.0
See the version list below for details.
dotnet add package ConfigBoundNET --version 0.1.0
NuGet\Install-Package ConfigBoundNET -Version 0.1.0
<PackageReference Include="ConfigBoundNET" Version="0.1.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="ConfigBoundNET" Version="0.1.0" />
<PackageReference Include="ConfigBoundNET"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add ConfigBoundNET --version 0.1.0
#r "nuget: ConfigBoundNET, 0.1.0"
#:package ConfigBoundNET@0.1.0
#addin nuget:?package=ConfigBoundNET&version=0.1.0
#tool nuget:?package=ConfigBoundNET&version=0.1.0
ConfigBoundNET
Compile-time validated configuration for .NET.
Annotate a partial record with [ConfigSection("…")] and ConfigBoundNET generates a validator, a DI extension method, and build-time diagnostics for free. Configuration bugs that used to surface in production — typos in section names, missing required keys, mistyped property names — now fail at build time or at host startup, never at 3 a.m.
The problem
The idiomatic ASP.NET Core configuration pattern is stringly-typed and silently lossy:
var conn = builder.Configuration["Db:Conn"]; // typo? nobody tells you
var timeout = builder.Configuration.GetValue<int>("Db:TO"); // wrong key? returns 0
Even the strongly-typed IOptions<T> pattern punts validation to runtime and requires boilerplate for every new section:
services.Configure<DbConfig>(config.GetSection("Db"));
services.AddSingleton<IValidateOptions<DbConfig>, DbConfigValidator>();
// …and you still have to write DbConfigValidator by hand.
The solution
Declare the shape once:
using ConfigBoundNET;
namespace MyApp;
[ConfigSection("Db")]
public partial record DbConfig
{
public string Conn { get; init; } = default!; // required (non-nullable)
public int CommandTimeoutSeconds { get; init; } = 30; // optional with default
public string? ReplicaConn { get; init; } // optional (nullable)
}
Register it in one line:
var builder = Host.CreateApplicationBuilder(args);
builder.Services
.AddDbConfig(builder.Configuration) // <- generated
.AddOptions<DbConfig>()
.ValidateOnStart();
ConfigBoundNET generates everything else at build time:
DbConfig.SectionName— a compile-timeconst stringequal to"Db".DbConfig.Validator— anIValidateOptions<DbConfig>that null/whitespace-checks every required property.DbConfigServiceCollectionExtensions.AddDbConfig(IServiceCollection, IConfiguration)— binds the section, registers the validator idempotently viaTryAddEnumerable, and wires change-token propagation soIOptionsMonitor<T>reacts to reloads.
If the connection string is missing from appsettings.json, the host fails at startup with:
OptionsValidationException: [Db:Conn] is required but was null, empty, or whitespace.
...instead of the usual NullReferenceException buried three layers deep in your data access code.
Install
ConfigBoundNET ships as a single analyzer package. There is no runtime dependency to add — the [ConfigSection] attribute is emitted into your own assembly at build time.
<ItemGroup>
<PackageReference Include="ConfigBoundNET" Version="0.1.0" />
</ItemGroup>
You will also need the standard IOptions<T> binding packages, which are already transitive in most ASP.NET Core / Generic Host apps:
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.5" />
How required fields are detected
ConfigBoundNET infers required-ness from the type system, so you almost never need extra attributes.
| Declaration | Treated as | Rationale |
|---|---|---|
public string Conn { get; init; } = …; |
Required | Non-nullable reference type. |
public string? Conn { get; init; } |
Optional | Nullable reference type — explicitly opted out. |
public required string Conn { get; init; } |
Required | C# 11 required modifier honored. |
public int Timeout { get; init; } |
Not checked | Value types bind defaults; layer DataAnnotations on top for bounds checks. |
Required reference-type properties get a null check. string properties additionally get an IsNullOrWhiteSpace check — empty strings in config are almost always deployment mistakes.
What the generator emits
For the DbConfig above, ConfigBoundNET emits roughly this (fully qualified names elided for readability):
// <auto-generated/>
partial record DbConfig
{
public const string SectionName = "Db";
public sealed class Validator : IValidateOptions<DbConfig>
{
public ValidateOptionsResult Validate(string? name, DbConfig options)
{
if (options is null)
return ValidateOptionsResult.Fail("DbConfig instance was null.");
var failures = new List<string>();
if (string.IsNullOrWhiteSpace(options.Conn))
failures.Add("[Db:Conn] is required but was null, empty, or whitespace.");
return failures.Count > 0
? ValidateOptionsResult.Fail(failures)
: ValidateOptionsResult.Success;
}
}
}
public static class DbConfigServiceCollectionExtensions
{
public static IServiceCollection AddDbConfig(
this IServiceCollection services,
IConfiguration configuration)
{
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IValidateOptions<DbConfig>, DbConfig.Validator>());
services.Configure<DbConfig>(configuration.GetSection(DbConfig.SectionName));
return services;
}
}
You can inspect the real output by setting <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> on your project — see examples/ConfigBoundNET.Example for a working setup.
Diagnostics
| ID | Severity | Meaning |
|---|---|---|
| CB0001 | Error | Type decorated with [ConfigSection] is missing the partial modifier. |
| CB0002 | Error | [ConfigSection("")] section name is null, empty, or whitespace. |
| CB0003 | Error | [ConfigSection] applied to a nested type (only top-level types allowed). |
| CB0004 | Warning | Annotated type has no public writable properties — nothing to bind. |
| CB0005 | Error | [ConfigSection] applied to a struct or other unsupported type kind. |
All diagnostics fire at build time. CB0001–CB0003 and CB0005 fail the build; CB0004 is advisory.
FAQ
Why a partial record?
The generator extends your type with a nested Validator class and a SectionName constant. That requires the partial modifier. Any reference type works — partial class is fine too — but records are a natural fit for immutable config.
Does this work with IOptionsMonitor<T> / reload-on-change?
Yes. ConfigBoundNET wires up the standard OptionsConfigurationServiceCollectionExtensions.Configure<T> path under the hood, which registers an IOptionsChangeTokenSource<T> against the configuration section. Reloads propagate normally.
What about nested configuration types (a DbConfig that contains a RetryPolicyConfig)?
Nested reference properties are bound by the stock ConfigurationBinder just like they would be in hand-written options. Only the outer type needs [ConfigSection] — inner types participate through standard binding.
Why does the attribute get stripped from my assembly metadata?
ConfigSectionAttribute is decorated with [Conditional("CONFIGBOUNDNET_KEEP_ATTRIBUTES")], which propagates to its usages. The source generator sees the attribute at build time through the syntax tree, but IL emission skips it unless you define that preprocessor symbol. Define it in your csproj if you need the attribute visible to runtime reflection.
Why does AddDbConfig return IServiceCollection rather than OptionsBuilder<T>?
So you can fluently chain other services.AddX(...) calls. If you want to tack extra validators onto the options pipeline, call services.AddOptions<DbConfig>() afterwards and chain from there (the generator's registration is idempotent).
Repository layout
ConfigBoundNET/
├── src/ConfigBoundNET/ # The incremental source generator (ships as NuGet)
├── tests/ConfigBoundNET.Tests/ # xUnit tests driving the generator directly
└── examples/ConfigBoundNET.Example/ # Minimal Generic Host app demonstrating end-to-end use
Build & test
dotnet build ConfigBoundNET.sln
dotnet test tests/ConfigBoundNET.Tests/ConfigBoundNET.Tests.csproj
Run the example
cd examples/ConfigBoundNET.Example
dotnet run
Expected output:
[Db] Conn = Server=localhost;Database=App;Trusted_Connection=True;
[Db] CommandTimeoutSeconds = 30
Try editing appsettings.json to remove the Conn value and re-run — the host now fails at startup with a precise, actionable error.
Contributing
Issues and PRs welcome. Please make sure dotnet test passes and that any new diagnostic IDs are listed in src/ConfigBoundNET/AnalyzerReleases.Unshipped.md.
License
GPL3. See LICENSE for details.
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.