HTTP polling provider for Cocoar.Configuration enabling remote configuration sources with configurable polling intervals, automatic retries, and failure sentinel values for resilient distributed configuration management.
$ dotnet add package Cocoar.Configuration.HttpPolling
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.
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.
dotnet add package Cocoar.Configuration
dotnet add package Cocoar.Configuration.AspNetCore
# Optional features:
dotnet add package Cocoar.Configuration.Secrets
dotnet add package Cocoar.Configuration.HttpPolling
dotnet add package Cocoar.Configuration.MicrosoftAdapterLinks: Cocoar.Configuration · AspNetCore · Secrets · HttpPolling · MicrosoftAdapter
Microsoft's IConfiguration works, but configuration deserves better. Here's what you get:
Configure<T>() calls, no IOptions<T> wrappers.IReactiveConfig<(T1, T2, T3)> means multiple configs stay in sync. Never see inconsistent state.IOptionsMonitor wiring.IConfigurationHealthService.Secret<T> with automatic zeroization and pre-encrypted envelope support.CocoarTestConfiguration. Works with direct instantiation, DI, AspNetCore, and WebApplicationFactory. See Testing Documentation.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.
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 */);
}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();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;
}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 });
});
}
}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 winsEnvironment variable mapping:
Hierarchical keys use __ (double underscore):
APP_Database__Host=localhost
APP_Database__Port=5432
# Maps to: AppSettings.Database.Host and AppSettings.Database.PortCommand-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=prodPrefix 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 - fails fast if missing, rolls back entire recompute
rule.For<CoreSettings>().FromFile("required.json").Required()
// Optional - graceful degradation (default)
// Returns empty object with C# defaults on failure, tracks failure in health
rule.For<OptionalSettings>().FromFile("optional.json")Behavior:
Unhealthy.{}, configuration gets C# property defaults, and the failure is tracked via health monitoring with status Degraded. The application continues running with defaults while the source is unavailable.Example - Optional rule with missing file:
public class FeatureConfig
{
public bool EnableNewUI { get; set; } = false; // C# default
public int MaxItems { get; set; } = 10; // C# default
}
rule.For<FeatureConfig>().FromFile("features.json") // File doesn't exist
var config = manager.GetConfig<FeatureConfig>();
// config is NOT null - returns instance with defaults (EnableNewUI=false, MaxItems=10)
// Health status is Degraded, LastFailureException tracks FileNotFoundExceptionThis enables graceful degradation - your application continues with safe defaults while monitoring alerts on the health status change.
GetConfig vs GetRequiredConfig:
GetConfig<T>() returns configuration if rules are defined for type T, never null when rules exist (returns empty object with defaults on first-time failures)GetRequiredConfig<T>() throws if no rules are defined for type T - it's a static configuration check, not a runtime availability check. Use this in config-aware rules to ensure dependent configuration is properly defined.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 decisionsrule.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 rulesbuilder.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) { }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:
Logging__LogLevel__Default=DebugSupports nested interfaces: If your interface properties contain other interface properties, just register all the mappings and they'll work at any depth.
__ for nesting)IConfiguration sources (Cocoar.Configuration.MicrosoftAdapter)⚠️ Developer Preview: API may change in future releases based on feedback. Production-ready but subject to refinement.
Cocoar.Configuration.Secrets provides memory-safe handling of sensitive configuration data — a unique capability in open-source configuration libraries:
Secret<T> type – Automatic zeroization of sensitive data in memorySecret<T>.Open() provides controlled exposure windows// Configuration with encrypted secrets
public class AppSettings
{
public string AppName { get; set; }
public Secret<DatabaseCredentials> DatabaseSecret { get; set; }
}
// Use secrets safely
using var exposed = settings.DatabaseSecret.Open();
var connectionString = BuildConnectionString(exposed.Value);
// Secret automatically zeroized when disposedResources:
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 |
| SecretsBasicExample | Memory-safe secret handling with Secret<T> |
| SecretsCertificateExample | Pre-encrypted secrets with X.509 certificates |
| Topic | Link |
|---|---|
| Reactive Configuration | Reactive Config |
| Health Monitoring | Health Monitoring |
| Testing with Configuration Overrides | Testing Overrides |
| Intelligent Certificate Caching | Certificate Caching |
| Provider Guidance | Provider Guidance |
| Migration from v2.x | Migration Guide v2→v3 |
| Migration from v1.x | Migration Guide v1→v2 |
Cocoar.Configuration provides first-class testing support with CocoarTestConfiguration:
[Fact]
public async Task TestWithOverriddenConfig()
{
// Set test configuration BEFORE creating WebApplicationFactory
CocoarTestConfiguration.ReplaceAllRules(rule => [
rule.For<DbConfig>().FromStatic(_ => testDbConfig)
]);
await using var factory = new WebApplicationFactory<Program>();
// Original rules are skipped - only test rules execute
}Over 200 automated tests covering:
See the Testing Guide for implementation details and Testing Overrides Documentation for integration testing patterns.
Contributions welcome! See CONTRIBUTING.md for guidelines.
To report security vulnerabilities, see SECURITY.md.
Best Practices:
Runtime Security Posture: