Cocoar.Configuration.AspNetCore
3.3.0
dotnet add package Cocoar.Configuration.AspNetCore --version 3.3.0
NuGet\Install-Package Cocoar.Configuration.AspNetCore -Version 3.3.0
<PackageReference Include="Cocoar.Configuration.AspNetCore" Version="3.3.0" />
<PackageVersion Include="Cocoar.Configuration.AspNetCore" Version="3.3.0" />
<PackageReference Include="Cocoar.Configuration.AspNetCore" />
paket add Cocoar.Configuration.AspNetCore --version 3.3.0
#r "nuget: Cocoar.Configuration.AspNetCore, 3.3.0"
#:package Cocoar.Configuration.AspNetCore@3.3.0
#addin nuget:?package=Cocoar.Configuration.AspNetCore&version=3.3.0
#tool nuget:?package=Cocoar.Configuration.AspNetCore&version=3.3.0
Reactive, strongly-typed configuration layering for .NET
Elevates configuration from hidden infrastructure to an observable, safetyβenforced subsystem you can trust under change and failure.
π Articles & Deep Dives
β’ Reactive, Strongly-Typed Configuration in .NET: Introducing Cocoar.Configuration v3.0 (Part 1)
Learn how v3.0 simplifies configuration management with zero-ceremony DI, atomic multi-config updates, and reactive patterns.β’ Config-Aware Rules in .NET β The Power Feature of Cocoar.Configuration (Part 2)
Dive deeper into atomic recompute, required vs optional rules, and config-aware conditional logic for dynamic, tenant-aware setups.
Shouldn't configuration be this easy?
builder.Services.AddCocoarConfiguration(rule => [
rule.For<AppSettings>().FromFile("appsettings.json").Select("App"),
rule.For<AppSettings>().FromEnvironment("APP_")
]);
It is. AppSettings is now injectable β and automatically updates when configs change.
Install
dotnet add package Cocoar.Configuration
dotnet add package Cocoar.Configuration.AspNetCore
# Optional providers:
dotnet add package Cocoar.Configuration.HttpPolling
dotnet add package Cocoar.Configuration.MicrosoftAdapter
Links: Cocoar.Configuration Β· AspNetCore Β· HttpPolling Β· MicrosoftAdapter
Why Cocoar.Configuration?
Microsoft's IConfiguration works, but configuration deserves better. Here's what you get:
- Zero ceremony β Define a class, add a rule, inject it. No
Configure<T>()calls, noIOptions<T>wrappers. - Atomic multi-config updates β
IReactiveConfig<(T1, T2, T3)>means multiple configs stay in sync. Never see inconsistent state. - Config-aware rules β Rules can access earlier config to make decisions. Perfect for multi-tenant and dynamic scenarios.
- Reactive by default β Subscribe to changes automatically. No manual
IOptionsMonitorwiring. - Explicit layering β Rules execute in order, last write wins. No hidden merge logic.
- Interface deserialization β Support for interface-typed properties in config classes with explicit mapping.
- Built-in health monitoring β Track provider status and config changes with
IConfigurationHealthService.
DI Lifetimes: Concrete config types are registered as Scoped (stable snapshot per request), while IReactiveConfig<T> is Singleton (continuous live updates). These defaults can be customized via the setup parameter.
Migration from IOptions
Before (IConfiguration + IOptions):
// Startup configuration
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("App"));
// Injection - requires wrapper
public class MyService(IOptions<AppSettings> options)
{
var settings = options.Value; // Unwrap every time
}
After (Cocoar.Configuration):
// Startup configuration
builder.Services.AddCocoarConfiguration(rule => [
rule.For<AppSettings>().FromFile("appsettings.json").Select("App")
]);
// Direct injection - no wrapper
public class MyService(AppSettings settings)
{
// Just use it
}
// Or reactive
public class MyService(IReactiveConfig<AppSettings> config)
{
config.Subscribe(newSettings => /* handle changes */);
}
Quick Example
var builder = WebApplication.CreateBuilder(args);
// Define your configuration rules
builder.Services.AddCocoarConfiguration(rule => [
rule.For<AppSettings>().FromFile("appsettings.json").Select("App"),
rule.For<AppSettings>().FromEnvironment("APP_"),
rule.For<DatabaseConfig>().FromFile("appsettings.json").Select("Database")
]);
var app = builder.Build();
// Direct injection - just use your POCO
app.MapGet("/api/status", (AppSettings settings) =>
new { settings.Version, settings.FeatureFlags });
app.Run();
For reactive scenarios (long-lived services):
public class CacheWarmer : BackgroundService
{
private readonly IReactiveConfig<AppSettings> _config;
public CacheWarmer(IReactiveConfig<AppSettings> config)
{
_config = config;
// Subscribe once - rebuild cache when settings change
_config.Subscribe(newSettings =>
{
Console.WriteLine($"Config changed, rebuilding cache...");
RebuildCache(newSettings);
});
}
protected override Task ExecuteAsync(CancellationToken stoppingToken) => Task.CompletedTask;
}
SignalR hub that streams atomic multi-config updates
public class ConfigHub : Hub
{
private readonly IReactiveConfig<(AppSettings App, DatabaseConfig Db)> _configs;
public ConfigHub(IReactiveConfig<(AppSettings App, DatabaseConfig Db)> configs)
{
_configs = configs;
// Stream config changes to connected clients - always atomic
_configs.Subscribe(async tuple =>
{
var (app, db) = tuple;
await Clients.All.SendAsync("ConfigUpdated", new { app.Version, db.ConnectionString });
});
}
}
What You Can Do
Layer Configuration Sources
builder.Services.AddCocoarConfiguration(rule => [
rule.For<AppSettings>().FromFile("appsettings.json"), // Base
rule.For<AppSettings>().FromFile("appsettings.Production.json"), // Environment
rule.For<AppSettings>().FromEnvironment("APP_"), // Overrides
rule.For<AppSettings>().FromCommandLine() // Final overrides (highest priority)
]);
// Rules execute in order - last write wins
Environment variable mapping:
Hierarchical keys use __ (double underscore):
APP_Database__Host=localhost
APP_Database__Port=5432
# Maps to: AppSettings.Database.Host and AppSettings.Database.Port
Command-line argument mapping:
Hierarchical keys use : or __:
dotnet run --Database:Host=localhost --Database:Port=5432 --Verbose
# Maps to: AppSettings.Database.Host, AppSettings.Database.Port, and AppSettings.Verbose (true)
Flexible switch prefixes for command-line arguments:
Use any prefix style (--, -, /, @, #, %) - even multiple at once:
// Single custom prefix
rule.For<AppConfig>().FromCommandLine(["-"]) // Unix-style
rule.For<AppConfig>().FromCommandLine(["/"]) // Windows-style
rule.For<AppConfig>().FromCommandLine(["@"]) // Custom semantic style
// Multiple prefixes simultaneously
rule.For<AppConfig>().FromCommandLine(["--", "-", "/"])
# Mix different styles in the same command line
dotnet run --host=localhost -port=8080 /verbose
# Or use semantic prefixes for self-documenting CLIs
invoke.exe @target=server #issue=123 %env=prod
Prefix filtering for command-line arguments:
Map arguments to specific configuration types:
builder.Services.AddCocoarConfiguration(rule => [
rule.For<AppConfig>().FromCommandLine("app_"),
rule.For<DatabaseConfig>().FromCommandLine("db_")
]);
dotnet run --app_host=localhost --db_connectionstring="Server=localhost"
# --app_host β AppConfig.Host (prefix stripped)
# --db_connectionstring β DatabaseConfig.ConnectionString (prefix stripped)
Required vs Optional Rules
// Required - fails fast if missing
rule.For<CoreSettings>().FromFile("required.json").Required()
// Optional - graceful degradation at runtime (default)
rule.For<OptionalSettings>().FromFile("optional.json")
Conditional Rules
rule.For<TenantSettings>().FromFile("tenant.json"),
rule.For<PremiumFeatures>().FromFile("premium.json")
.When(accessor => accessor.GetRequiredConfig<TenantSettings>().IsPremium)
// Rules can access earlier config to make decisions
Dynamic Configuration
rule.For<TenantSettings>().FromFile("tenant.json"),
rule.For<ApiSettings>().FromHttpPolling(accessor =>
{
var tenant = accessor.GetRequiredConfig<TenantSettings>();
return new HttpPollingRuleOptions(
$"https://{tenant.Region}.api.example.com/config",
pollInterval: TimeSpan.FromMinutes(5)
);
})
// Rules can derive behavior from earlier rules
Interface Exposure
builder.Services.AddCocoarConfiguration(rule => [
rule.For<AppSettings>().FromFile("appsettings.json")
], setup => [
setup.ConcreteType<AppSettings>().ExposeAs<IAppSettings>()
]);
// Both AppSettings and IAppSettings are injectable
public class MyService(IAppSettings settings) { }
Interface Deserialization
When your configuration classes have interface-typed properties, you need to map them to concrete types for JSON deserialization:
// Configuration with interface properties
public class AppSettings
{
public string AppName { get; set; }
public ILoggingConfig Logging { get; set; } // Interface property!
}
builder.Services.AddCocoarConfiguration(rule => [
rule.For<AppSettings>().FromEnvironment() // or FromFile, FromHttpPolling, etc.
], setup => [
// Map interface to concrete type for deserialization
setup.Interface<ILoggingConfig>().DeserializeTo<LoggingConfig>()
]);
Why is this needed? When loading configuration from JSON sources (files, environment variables, HTTP), properties typed as interfaces cannot be deserialized directly. This mapping tells the deserializer which concrete type to instantiate.
Common scenarios:
- Environment variables:
Logging__LogLevel__Default=Debug - Visual Studio hot reload injecting logging configuration
- Modular configuration with abstracted dependencies
Supports nested interfaces: If your interface properties contain other interface properties, just register all the mappings and they'll work at any depth.
Providers
- File β JSON files with automatic change detection and reload
- Environment Variables β Prefix-based with hierarchical mapping (
__for nesting) - Command-Line Arguments β POSIX-style parsing with prefix support and nested configuration
- HTTP Polling β Remote config with caching and change detection (Cocoar.Configuration.HttpPolling)
- Microsoft Adapter β Bridge existing
IConfigurationsources (Cocoar.Configuration.MicrosoftAdapter) - Static/Observable β In-memory for testing and development
Examples
Explore real-world scenarios in the examples directory:
| Example | Description |
|---|---|
| BasicUsage | ASP.NET Core with file + environment layering |
| FileLayering | Multi-file layering (base/env/local) |
| DynamicDependencies | Rules derived from earlier config |
| ConditionalRulesExample | Config-aware conditional rules |
| TupleReactiveExample | Atomic multi-config snapshots |
| HttpPollingExample | Remote HTTP config polling |
| MicrosoftAdapterExample | Integrate existing IConfiguration sources |
Documentation
| Topic | Link |
|---|---|
| Getting Started | Quickstart Guide |
| Architecture & How It Works | Architecture |
| Provider Details | Providers |
| Reactive Configuration | Reactive Config |
| Health Monitoring | Health Monitoring |
| Interface Binding | Binding |
| Migration from v2.x | Migration Guide |
| Advanced Scenarios | Deep Dive |
Testing
Over 200 automated tests covering:
- Configuration layering and rule execution
- Reactive updates and atomic snapshots
- Provider reliability and failover
- Concurrent access and race conditions
- Health monitoring and status tracking
See the Testing Guide for details.
Contributing
Contributions welcome! See CONTRIBUTING.md for guidelines.
- Semantic Versioning β Breaking changes increment major version
- Apache 2.0 License β See LICENSE and NOTICE
- Trademarks β See TRADEMARKS.md
Security
- Don't commit secrets to config files
- Use environment variables or secure providers for sensitive data
- Enable TLS/HTTPS for remote config endpoints
- Consider Azure Key Vault or similar via the Microsoft Adapter
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. 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. |
-
net9.0
- Cocoar.Configuration (>= 3.3.0)
- Cocoar.Configuration.DI (>= 3.3.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 |
|---|---|---|
| 3.3.0 | 175 | 10/23/2025 |
| 3.2.0 | 166 | 10/23/2025 |
| 3.1.1 | 166 | 10/19/2025 |
| 3.1.0 | 157 | 10/19/2025 |
| 3.0.0 | 172 | 10/12/2025 |
| 2.0.0 | 104 | 10/11/2025 |
| 1.1.0 | 173 | 9/25/2025 |
| 1.0.0 | 203 | 9/21/2025 |
| 0.15.0 | 291 | 9/18/2025 |
| 0.14.0 | 310 | 9/17/2025 |
| 0.13.0 | 315 | 9/17/2025 |
| 0.12.0 | 308 | 9/17/2025 |
| 0.11.1 | 297 | 9/16/2025 |
| 0.11.0 | 298 | 9/16/2025 |
| 0.10.0 | 302 | 9/16/2025 |
| 0.9.2 | 246 | 9/15/2025 |
| 0.9.1 | 221 | 9/14/2025 |
| 0.9.0 | 144 | 9/14/2025 |
| 0.4.0 | 158 | 9/13/2025 |
Full release notes: https://github.com/cocoar-dev/cocoar.configuration/blob/develop/CHANGELOG.md