ConfigBoundNET 0.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package ConfigBoundNET --version 0.1.0
                    
NuGet\Install-Package ConfigBoundNET -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ConfigBoundNET" Version="0.1.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ConfigBoundNET" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="ConfigBoundNET">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ConfigBoundNET --version 0.1.0
                    
#r "nuget: ConfigBoundNET, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ConfigBoundNET@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ConfigBoundNET&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=ConfigBoundNET&version=0.1.0
                    
Install as a Cake Tool

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-time const string equal to "Db".
  • DbConfig.Validator — an IValidateOptions<DbConfig> that null/whitespace-checks every required property.
  • DbConfigServiceCollectionExtensions.AddDbConfig(IServiceCollection, IConfiguration) — binds the section, registers the validator idempotently via TryAddEnumerable, and wires change-token propagation so IOptionsMonitor<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.

There are no supported framework assets in this package.

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.

Version Downloads Last Updated
2.4.2 128 5/10/2026
2.4.1 102 4/30/2026
2.4.0 87 4/20/2026
2.3.0 94 4/19/2026
2.2.0 104 4/19/2026
2.1.0 92 4/18/2026
2.0.1 96 4/17/2026
2.0.0 97 4/12/2026
1.0.0 95 4/10/2026
0.1.0 100 4/9/2026