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 providers:
dotnet add package Cocoar.Configuration.HttpPolling
dotnet add package Cocoar.Configuration.MicrosoftAdapterLinks: Cocoar.Configuration · AspNetCore · 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.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
rule.For<CoreSettings>().FromFile("required.json").Required()
// Optional - graceful degradation at runtime (default)
rule.For<OptionalSettings>().FromFile("optional.json")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)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 |
| 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 |
Over 200 automated tests covering:
See the Testing Guide for details.
Contributions welcome! See CONTRIBUTING.md for guidelines.