BITS.Generators.AppSettings
1.0.2
Prefix Reserved
dotnet add package BITS.Generators.AppSettings --version 1.0.2
NuGet\Install-Package BITS.Generators.AppSettings -Version 1.0.2
<PackageReference Include="BITS.Generators.AppSettings" Version="1.0.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="BITS.Generators.AppSettings" Version="1.0.2" />
<PackageReference Include="BITS.Generators.AppSettings"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add BITS.Generators.AppSettings --version 1.0.2
#r "nuget: BITS.Generators.AppSettings, 1.0.2"
#:package BITS.Generators.AppSettings@1.0.2
#addin nuget:?package=BITS.Generators.AppSettings&version=1.0.2
#tool nuget:?package=BITS.Generators.AppSettings&version=1.0.2
BITS.Generators.AppSettings
A modern C# source generator that creates strongly-typed, AOT-compatible configuration classes from your appsettings.json files with zero runtime reflection.
โจ Features
- ๐ Zero Runtime Overhead - All code generated at compile time
- ๐ฏ Strongly-Typed - Full IntelliSense and compile-time type checking
- โก AOT Compatible - Works perfectly with Native AOT for maximum performance
- ๐ JSON Pointer Resolution - Reference values with
$ConnectionStrings:Primarysyntax - ๐ Compile-Time Validation - Analyzer detects missing configuration at build time
- ๐ฆ Easy Integration - Drop in and start using immediately
- ๐งฉ Nested Sections - Support for complex hierarchical configuration
- ๐จ Flexible Mapping - Custom JSON paths and section names
๐ฆ Installation
Install via NuGet Package Manager:
dotnet add package BITS.Generators.AppSettings
Or via Package Manager Console:
Install-Package BITS.Generators.AppSettings
๐ Quick Start
Step 1: Define Your Configuration Interface
Create an interface and mark it with [AppSettings]:
using BITS.Generators.AppSettings;
[AppSettings]
public interface IDatabaseConfig
{
string ConnectionString { get; }
int MaxRetries { get; }
int Timeout { get; }
}
Step 2: Add Configuration to appsettings.json
{
"Database": {
"ConnectionString": "Server=localhost;Database=MyApp;",
"MaxRetries": 3,
"Timeout": 30
}
}
Step 3: Register and Use Configuration
The generator creates extension methods for easy registration:
// โ
Recommended: Use generated extension method
services.AddAppSettings(configuration);
// Or register individual configurations
services.AddDatabaseConfig(configuration);
The generator also creates a Create() method for manual binding:
// Alternative: Manual binding (also AOT-compatible)
var databaseConfig = DatabaseConfig.Create(configuration, "Database");
services.AddSingleton<IDatabaseConfig>(databaseConfig);
๐ฏ What Gets Generated
For the interface above, the generator creates:
/// <summary>
/// Strongly-typed configuration for Database section.
/// Generated from IDatabaseConfig interface.
/// </summary>
public sealed record DatabaseConfig(
string ConnectionString,
int MaxRetries,
int Timeout
) : IDatabaseConfig
{
/// <summary>
/// Creates a new instance from IConfiguration.
/// </summary>
public static DatabaseConfig Create(
IConfiguration configuration,
string? sectionOverride = null)
{
var section = configuration.GetSection(sectionOverride ?? "Database");
return new DatabaseConfig(
ConnectionString: ConfigurationHelper.GetValueFromSection<string>(
configuration, section, "ConnectionString", isRequired: true),
MaxRetries: ConfigurationHelper.GetValueFromSection<int>(
configuration, section, "MaxRetries", isRequired: true),
Timeout: ConfigurationHelper.GetValueFromSection<int>(
configuration, section, "Timeout", isRequired: true)
);
}
}
Extension Methods for Dependency Injection
The generator automatically creates extension methods for easy service registration:
Register Individual Configurations
// Register a specific configuration
services.AddDatabaseConfig(configuration);
services.AddLoggingConfig(configuration);
Register All Configurations
// Register only ROOT-level configurations (default behavior)
// Root configs are those NOT referenced by [Section] attributes in other configs
services.AddAppSettings(configuration);
// Register ALL configurations including nested subsections
services.AddAppSettings(configuration, registerNested: true);
When to use registerNested: true?
Default (
registerNested: false): Use when you only want root-level configs in DI container- Example: Register
IDatabaseConfigbut NOTIFeaturesConfigorIConnectionStringsConfig(if they're nested) - Nested configs are still accessible via their parent properties
- Example: Register
registerNested: true: Use when you want ALL configs available for injection- Example: Inject
IFeaturesConfigdirectly without going throughIDatabaseConfig.Features - Useful for modular architectures where components need direct access to subsections
- Example: Inject
Example:
// Configuration interfaces
[AppSettings]
public interface IDatabaseConfig
{
string Host { get; }
int Port { get; }
[Section("Database:Features")]
IFeaturesConfig Features { get; } // โ IFeaturesConfig is NESTED
}
[AppSettings]
public interface IFeaturesConfig
{
bool CacheEnabled { get; }
}
// Service registration
services.AddAppSettings(configuration); // Only IDatabaseConfig registered
// Access: dbConfig.Features.CacheEnabled โ
services.AddAppSettings(configuration, registerNested: true); // Both registered
// Access: featuresConfig.CacheEnabled โ
๏ฟฝ๐ฅ Advanced Features
Smart Section Names
No need to specify section names - they're automatically derived:
[AppSettings] // Section: "Database" (strips "I" and "Config")
public interface IDatabaseConfig { }
[AppSettings] // Section: "Logging" (strips "I" and "Settings")
public interface ILoggingSettings { }
[AppSettings] // Section: "Features" (strips "I", "Config", and "s")
public interface IFeaturesConfigs { }
Auto-stripped suffixes: Settings, Setting, Configs, Config, Configuration, Configurations
JSON Pointer References
Reuse configuration values with JSON pointers using $ prefix:
{
"ConnectionStrings": {
"Primary": "Server=primary-db;Database=MainDB;",
"Secondary": "$ConnectionStrings:Primary",
"Backup": "$ConnectionStrings:Secondary"
}
}
[AppSettings]
public interface IConnectionStrings
{
string Primary { get; }
string Secondary { get; } // Resolves to Primary value
string Backup { get; } // Resolves to Secondary (which resolves to Primary)
}
Nested Subsections
Handle complex nested configurations:
[AppSettings]
public interface IDatabaseConfig
{
string Host { get; }
int Port { get; }
[Section("Database:Features")]
IFeaturesConfig Features { get; }
[Section("Database:ConnectionStrings")]
IConnectionStrings ConnectionStrings { get; }
}
[AppSettings]
public interface IFeaturesConfig
{
bool CacheEnabled { get; }
int CacheExpiration { get; }
}
Interface Inheritance
Configuration interfaces can inherit from base interfaces:
// Base configuration interface (not marked with [AppSettings])
public interface IBaseConfig
{
string Environment { get; }
bool EnableLogging { get; }
}
// Application configuration inheriting from base
[AppSettings(Section = "App")]
public interface IAppConfig : IBaseConfig
{
string AppName { get; }
string Version { get; }
}
appsettings.json:
{
"App": {
"Environment": "Production",
"EnableLogging": true,
"AppName": "MyApplication",
"Version": "1.0.0"
}
}
Generated code includes all properties:
public sealed record AppConfig(
string Environment, // From IBaseConfig
bool EnableLogging, // From IBaseConfig
string AppName, // From IAppConfig
string Version // From IAppConfig
) : IAppConfig, IBaseConfig // Implements both interfaces
{
public static AppConfig Create(IConfiguration configuration, string? sectionOverride = null)
{
// Binds all properties from App section
}
}
Use with inheritance and subsections:
[AppSettings(Section = "App")]
public interface IAppConfig : IBaseConfig
{
string AppName { get; }
// Nested subsection
[Section("Database:Credentials")]
IDatabaseCredentials Credentials { get; }
}
[AppSettings(Section = "Database:Credentials")]
public interface IDatabaseCredentials
{
string Username { get; }
string Password { get; }
}
Custom JSON Paths
Map properties to specific JSON locations:
[AppSettings]
public interface IAppConfig
{
[JsonPath("ConnectionStrings:Default")]
string DefaultConnection { get; }
[JsonPath("App:Name")]
string ApplicationName { get; }
string Version { get; } // Maps to AppConfig:Version by default
}
Optional (Nullable) Properties
Support for nullable types:
[AppSettings]
public interface IOptionalConfig
{
string RequiredValue { get; } // Must be in appsettings.json
string? OptionalValue { get; } // Can be null or missing
int? OptionalNumber { get; } // Can be null or missing
}
Parent Sections
Organize configuration with parent-child relationships:
[AppSettings(ParentSection = "Logging")]
public interface ILogLevelConfig
{
string Default { get; }
string System { get; }
}
Maps to:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Warning"
}
}
}
๐ Compile-Time Validation
The built-in analyzer (BITS_APP_SETTINGS) validates your configuration at build time:
[AppSettings]
public interface IDatabaseConfig
{
string ConnectionString { get; }
int MaxRetries { get; }
}
If appsettings.json is missing MaxRetries:
error BITS_APP_SETTINGS: Configuration section 'Database' is missing required properties: MaxRetries
Configure severity in .editorconfig:
# Make it a warning instead of error
dotnet_diagnostic.BITS_APP_SETTINGS.severity = warning
# Or disable it
dotnet_diagnostic.BITS_APP_SETTINGS.severity = none
โก AOT Compatibility
Fully compatible with Native AOT - no reflection required:
// โ
AOT-compatible - uses generated code
var config = DatabaseConfig.Create(configuration, "Database");
// โ Not AOT-compatible - uses reflection
var config = configuration.GetSection("Database").Get<DatabaseConfig>();
Supported Types (AOT-Compatible)
All conversions are handled without reflection:
- Primitives:
string,bool,int,long,double,decimal,float - Unsigned:
uint,ulong,ushort,byte,sbyte - Date/Time:
DateTime,DateTimeOffset,TimeSpan - Other:
Guid,enumtypes - Nullable: All above types as
Nullable<T>orT?
๐ Complete Example
appsettings.json:
{
"Database": {
"Host": "localhost",
"Port": 5432,
"ConnectionString": "Server=$Database:Host;Port=$Database:Port;Database=MyApp",
"Features": {
"CacheEnabled": true,
"CacheExpiration": 300
}
},
"Logging": {
"Level": "Information",
"EnableConsole": true
}
}
Configuration Interfaces:
[AppSettings]
public interface IDatabaseConfig
{
string Host { get; }
int Port { get; }
string ConnectionString { get; } // Resolves $Database:Host and $Database:Port
[Section("Database:Features")]
IFeaturesConfig Features { get; }
}
[AppSettings]
public interface IFeaturesConfig
{
bool CacheEnabled { get; }
int CacheExpiration { get; }
}
[AppSettings]
public interface ILoggingConfig
{
string Level { get; }
bool EnableConsole { get; }
}
Usage:
var builder = WebApplication.CreateBuilder(args);
// Register configurations
builder.Services.AddSingleton<IDatabaseConfig>(
DatabaseConfig.Create(builder.Configuration, "Database"));
builder.Services.AddSingleton<ILoggingConfig>(
LoggingConfig.Create(builder.Configuration, "Logging"));
var app = builder.Build();
// Use in your services
public class MyService
{
private readonly IDatabaseConfig _dbConfig;
public MyService(IDatabaseConfig dbConfig)
{
_dbConfig = dbConfig;
}
public void DoWork()
{
Console.WriteLine($"Connecting to {_dbConfig.Host}:{_dbConfig.Port}");
Console.WriteLine($"Cache enabled: {_dbConfig.Features.CacheEnabled}");
}
}
๐ ๏ธ Requirements
- .NET 10 SDK or later
- C# 14 or later
- Works with: Console Apps, ASP.NET Core, Worker Services, etc.
๐ API Reference
[AppSettings] Attribute
Marks an interface for configuration generation.
Properties:
Section(optional): Configuration section name (default: auto-derived from interface name)ParentSection(optional): Parent section path
Example:
[AppSettings(Section = "CustomName", ParentSection = "Parent")]
public interface IMyConfig { }
[JsonPath] Attribute
Specifies a custom JSON path for a property.
Example:
[JsonPath("Nested:Deep:Value")]
string MyProperty { get; }
[Section] Attribute
Alternative to [JsonPath] for specifying section paths on properties.
Example:
[Section("Database:Advanced")]
IAdvancedConfig Advanced { get; }
๐งช Testing
The project includes comprehensive tests using TUnit:
# Run all tests
dotnet test
# Run with detailed output
dotnet test --verbosity detailed
๐ฆ Samples
Check out the samples/ directory for complete working examples:
- AppSettingsConsole - Basic usage with DI
- AdvancedFeaturesExample - Nested sections and pointer resolution
- InheritanceExample - Interface inheritance patterns
# Run the advanced example
dotnet run --project samples/AdvancedFeaturesExample
๐ค Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new features
- Ensure all tests pass
- Follow the existing code style (see
.editorconfig) - Submit a pull request
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgments
- Built with Roslyn Source Generators
- Testing with TUnit
- JSON parsing with System.Text.Json
๐ Support
- ๐ Documentation
- ๐ Issue Tracker
- ๐ฌ Discussions
Made with โค๏ธ by Blue IT Systems
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.Text.Json (>= 10.0.0)
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 |
|---|
See CHANGELOG.md for detailed release notes.