⚙️Configuration deserves better.
In .NET, configuration isn’t new — Microsoft’s IConfiguration
and IOptions<T>
have done a decent job for years.
Add a JSON file, overlay some environment variables — done.
Here's the thing: configuration is often treated as an underdog.
Just some JSON files, some environment variables, a bit of plumbing in Program.cs
— nothing worth thinking deeply about.
In reality, configuration is much more important. It is the control plane of your app. It decides which features are enabled, how your logging behaves, what limits apply, and which backend systems you talk to.
That's why I believe configuration deserves more: it should be a first-class subsystem — observable, type-safe, and something you can trust under change and failure.
Over time I kept hitting the same pain points that Microsoft’s system couldn’t solve cleanly — Cocoar.Configuration was born from closing those gaps.
🧵 This is Part 1 of the Cocoar.Configuration Series:
1️⃣ Announcement ← you are here
2️⃣ Deep Dive (coming soon)
3️⃣ Real-World Scenarios (coming soon)
4️⃣ …and more to follow
🚀 Quick Start
A complete example in under a minute:
using Cocoar.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCocoarConfiguration(rule => [
rule.For<AppSettings>().FromFile("appsettings.json").Select("App"),
rule.For<AppSettings>().FromEnvironment("APP_")
], setup => [
setup.ConcreteType<AppSettings>().ExposeAs<IAppSettings>()
]);
var app = builder.Build();
app.MapGet("/config", (IAppSettings s)
=> Results.Ok(new { s.ApiUrl, s.Timeout }));
app.Run();
// --- your config types ---
public interface IAppSettings { string ApiUrl { get; } int Timeout { get; } }
public record AppSettings(string ApiUrl, int Timeout) : IAppSettings;
appsettings.json
{
"App": { "ApiUrl": "https://api.example.com", "Timeout": 30 }
}
Then run it and test:
curl http://localhost:5000/config
# {"apiUrl":"https://api.example.com","timeout":30}
💡 Change the JSON while the app runs — hit the endpoint again and watch it update instantly!
⚙️ What Makes It Different
1️⃣ Type-Safe by Default
No IOptions<T>
ceremony or manual binding.
Just clean injection of your config types or interfaces.
Microsoft style:
builder.Services.Configure<AppSettings>(
builder.Configuration.GetSection("App"));
builder.Services.AddScoped<IAppSettings>(sp =>
sp.GetRequiredService<IOptions<AppSettings>>().Value);
Cocoar style:
builder.Services.AddCocoarConfiguration(rule => [
rule.For<AppSettings>().FromFile("appsettings.json").Select("App"),
rule.For<AppSettings>().FromEnvironment("APP_")
], setup => [
setup.ConcreteType<AppSettings>().ExposeAs<IAppSettings>()
]);
Interfaces work natively.
Layering is explicit.
Registration is automatic.
2️⃣ Atomic Multi-Config Updates
IOptionsMonitor<T>
notifies each type separately — you can see new AppSettings
with old DbSettings
.
That’s inconsistent state.
Cocoar.Configuration uses tuple-reactive updates so multiple configs change atomically:
public class MyService
{
public MyService(IReactiveConfig<(AppSettings App, DbSettings Db)> config)
{
config.Subscribe(tuple =>
{
var (app, db) = tuple;
// both updated in one atomic pass
});
}
}
Whenever any provider changes, all affected types recompute together → no cross-type mismatch.
3️⃣ Reactive by Design
Every configured type is automatically reactive.
You can subscribe and react to live updates:
public class ApiClient
{
public ApiClient(IReactiveConfig<AppSettings> config, HttpClient http)
{
config.Subscribe(newSettings =>
{
Console.WriteLine($"Config changed → {newSettings.ApiUrl}");
});
}
}
Rebuild clients, re-connect services — everything stays in sync.
4️⃣ Built-In Health Monitoring
Out-of-the-box health checks keep you informed when sources fail or recover.
public class ConfigHealthCheck : IHealthCheck
{
private readonly IConfigurationHealthService _health;
public Task<HealthCheckResult> CheckHealthAsync(...)
{
var down = _health.Snapshot.Rules
.Where(r => r.Status == RuleResultStatus.Down && r.Required);
return Task.FromResult(
down.Any()
? HealthCheckResult.Unhealthy()
: HealthCheckResult.Healthy());
}
}
Health states include Initializing, Healthy, Degraded, Failed, Recovering — all integrated with ASP.NET Core health checks.
📊 Quick Comparison
Feature | Microsoft Config + IOptions | Cocoar.Configuration |
---|---|---|
Type Safety | Manual binding | Automatic |
DI Registration | Explicit Configure<T>
|
Automatic (scoped) |
Interface Injection | Manual factory | Native .ExposeAs<>()
|
Reactivity | Per-type only | Tuple-reactive atomic |
Layering | Implicit (provider order) | Explicit (rule order) |
Health Monitoring | Manual | Built-in |
🧩 When to Use It
✅ Use Cocoar.Configuration if you:
- Need reactive, dynamic config
- Combine multiple sources (file + env + HTTP)
- Care about health and observability
- Want interface-based, type-safe injection
- Have multi-tenant or dependent configs
- Require atomic updates across types
❌ Maybe skip it if you:
- Load static config once and forget it
- Only have a few simple settings
- Don’t need reactivity or health tracking
📦 Installation
Core
dotnet add package Cocoar.Configuration
dotnet add package Cocoar.Configuration.AspNetCore
dotnet add package Cocoar.Configuration.DI
Optional providers
dotnet add package Cocoar.Configuration.HttpPolling
dotnet add package Cocoar.Configuration.MicrosoftAdapter
🔜 What’s Next
This is just the beginning. Coming up next:
- Part 2 – Deep Dive: How rules, layers, and atomic recomputation work
- Part 3 – Real World Scenarios: Multi-tenant setups, secrets, graceful fallbacks
- Later: Design philosophy & integration stories from production
⭐ Star the repo or follow me on Dev.to to get notified when the next parts drop!
📚 Resources
License: Apache 2.0
Built by a developer who loves architecture more than ceremony.
— Bernhard / @cocoar-dev
Try it out and let me know what you think 🚀
Top comments (0)