https://github.com/Atulin/ConfigBinder
Zero-reflection configuration binding library for .NET, powered by Roslyn Source Generators. It allows you to bind strongly typed configuration classes directly from `IConfiguration` at compile time, eliminating the startup overhead and reflection cost
https://github.com/Atulin/ConfigBinder
config csharp dotnet ioptions source-generators
Last synced: about 12 hours ago
JSON representation
Zero-reflection configuration binding library for .NET, powered by Roslyn Source Generators. It allows you to bind strongly typed configuration classes directly from `IConfiguration` at compile time, eliminating the startup overhead and reflection cost
- Host: GitHub
- URL: https://github.com/Atulin/ConfigBinder
- Owner: Atulin
- License: mit
- Created: 2026-06-29T23:28:53.000Z (about 1 month ago)
- Default Branch: master
- Last Pushed: 2026-07-03T10:32:13.000Z (about 1 month ago)
- Last Synced: 2026-07-26T09:33:28.675Z (11 days ago)
- Topics: config, csharp, dotnet, ioptions, source-generators
- Language: C#
- Homepage:
- Size: 70.3 KB
- Stars: 3
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- csharp-source-generators - ConfigBinder -   Source-generated zero-reflection configuration binding for `IOptions<T>` pattern, with optional validation using `Immediate.Validation` (Source Generators / Statically typed resources / configurations)
README
# ConfigBinder
[](https://www.nuget.org/packages/Atulin.ConfigBinder)
[](LICENSE)
**ConfigBinder** is a zero-reflection, highly performant configuration binding library for .NET,
powered by Roslyn Source Generators. It allows you to bind strongly typed configuration classes directly from `IConfiguration`
at compile time, eliminating the startup overhead and reflection cost associated with the built-in `Microsoft.Extensions.Configuration` binder.
## Features
- **Zero Reflection**: Uses Roslyn Source Generators to generate binding code at compile time.
- **AOT / Trimming Friendly**: Completely avoids reflection, making it perfect for Native AOT and heavily trimmed applications.
- **Direct Access or IOptions**: Choose between injecting your raw configuration objects directly (`AddSingleton`) or using the standard `IOptions` pattern.
- **Immediate.Validations Integration**: Out-of-the-box support for validating configurations on startup using `Immediate.Validations`.
- **Custom Converters**: Define property-level or global custom type converters for complex types.
- **Built-in Parsing**: Automatically parses all standard primitives, enums, and any type implementing `IParsable`.
## Installation
Add the `ConfigBinder` package to your project. Since this is a source generator, you may want to reference it accordingly
(though it provides attributes as well).
```xml
```
## Quick Start
### 1. Define your Configuration Model
Decorate your configuration class, struct, or record with `[ConfigSection("SectionName")]`. By default,
properties must have an accessible setter (or `init`).
```csharp
using ConfigBinder.Attributes;
[ConfigSection("MyConfig")]
public sealed class MyConfig
{
public required string Name { get; init; }
public required int MaxRetries { get; init; }
}
```
### 2. Register Generated Configurations
In your `Program.cs` or startup code, call the generated extension method `RegisterGeneratedConfigs` on your `IServiceCollection`.
```csharp
var builder = WebApplication.CreateBuilder(args);
// This single call registers all types decorated with [ConfigSection]
builder.Services.RegisterGeneratedConfigs(builder.Configuration);
var app = builder.Build();
// You can now resolve your config!
var config = app.Services.GetRequiredService>();
```
## Registration Modes
ConfigBinder supports two modes for registering your configuration objects:
- `RegistrationMode.DirectAccess`: Registers the object directly as a singleton (`services.AddSingleton`).
- `RegistrationMode.Options` (Default): Registers the object using the standard Options pattern (`services.AddOptions()`).
You can override the mode on a per-class basis:
```csharp
[ConfigSection("MyConfig", Mode = ConfigRegistrationMode.DirectAccess)]
public class MyOptionsConfig { /* ... */ }
```
Or set an assembly-wide default:
```csharp
[assembly: ConfigSectionDefaults(Mode = ConfigRegistrationMode.DirectAccess)]
```
## Custom Converters
If you need to parse complex types that don't implement `IParsable`, you can write custom converters.
A converter is simply a type with a static method (default name `Convert`) taking a `string` and a `string` (property name)
and returning the parsed type.
### Property-Level Converter
```csharp
[ConfigSection("Feature")]
public class FeatureConfig
{
[ConfigConverter(typeof(MyCustomParser), "ParseMyType")]
public MyType SomeProperty { get; set; }
}
```
### Global Converter
Register a converter for a specific type across your entire assembly:
```csharp
[assembly: ConfigTypeConverter(typeof(MyType), typeof(MyCustomParser))]
```
## Validation
If your project references [Immediate.Validations](https://github.com/ImmediatePlatform/Immediate.Validations)
and your configuration type implements `IValidationTarget`, `ConfigBinder` will automatically wire up `IValidateOptions`
when using `RegistrationMode.Options`. This ensures your configuration is strictly validated on application startup.
> [!WARNING]
> Validation works only in `RegistrationMode.Options` mode.
```csharp
using Immediate.Validations.Shared;
[Validate]
[ConfigSection("ValidatedConfig")]
public sealed partial class ValidatedConfig : IValidationTarget
{
public required string Host { get; init; }
public required int Port { get; init; }
}
```
## Example
A config model like
```csharp
[Validate]
[ConfigSection("Validated")]
internal sealed partial class ValidatedConfig : IValidationTarget
{
[MinLength(10)]
public required string Name { get; init; }
public required float Weight { get; init; }
public required DateTime BuildDate { get; init; }
}
```
will generate the following binding code:
```csharp
internal static class ValidatedConfigConfigBinder
{
public static ValidatedConfig Bind(IConfiguration configuration)
{
var section = configuration.GetSection("Validated");
var instance = new ValidatedConfig {
Name = ValidateString(section["Name"], "Name"),
Weight = ParseFloat(section["Weight"], "Weight"),
BuildDate = ParseIParsable(section["BuildDate"], "BuildDate"),
};
return instance;
}
private static string ValidateString(string? value, string propertyName)
{
if (string.IsNullOrEmpty(value))
{
throw Required(propertyName);
}
return value;
}
private static T ParseIParsable(string? value, string propertyName) where T : IParsable
{
if (string.IsNullOrEmpty(value))
{
throw Required(propertyName);
}
if (T.TryParse(value, CultureInfo.InvariantCulture, out var t))
{
return t;
}
throw BadValue(propertyName, value, typeof(T).Name);
}
private static float ParseFloat(string? value, string propertyName)
{
if (string.IsNullOrEmpty(value))
{
throw Required(propertyName);
}
if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var n))
{
return n;
}
throw BadValue(propertyName, value, "float");
}
private static InvalidOperationException Required(string key) =>
new($"Required configuration key '{key}' is missing or empty");
private static InvalidOperationException BadValue(string key, string? value, string type) =>
new($"Configuration key '{key}' value '{value}' cannot be parsed as '{type}'");
}
```
and the following extension method:
```csharp
public static class GeneratedConfigRegistration
{
public static IServiceCollection RegisterGeneratedConfigs(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddSingleton>(sp =>
new ConfigBinderOptionsFactory(
sp.GetRequiredService>>(),
sp.GetRequiredService>>(),
sp.GetRequiredService>>(),
_ => ValidatedConfigConfigBinder.Bind(configuration)));
services.AddSingleton, ImmediateValidationOptionsValidator>();
services.AddOptions().ValidateOnStart();
return services;
}
}
```