Lightweight .NET configuration validation library with fail-fast startup validation. Validates IOptions<T> configurations at application startup with fluent API and comprehensive validation rules.
$ dotnet add package Fox.ConfigKitLightweight .NET configuration validation library with fail-fast startup validation
Fox.ConfigKit validates IOptions<T> configurations at application startup, catching errors before they cause runtime failures.
dotnet add package Fox.ConfigKit
NuGet Package Manager:
Install-Package Fox.ConfigKit
PackageReference:
<PackageReference Include="Fox.ConfigKit" Version="1.0.0" />
Configuration errors are caught at application startup, not at runtime:
builder.Services.AddConfigKit<DatabaseConfig>("Database")
.NotEmpty(c => c.ConnectionString, "Connection string is required")
.InRange(c => c.MaxPoolSize, 1, 1000, "Max pool size must be between 1 and 1000")
.ValidateOnStartup();
// Application won't start if configuration is invalid!
Type-safe, chainable validation rules:
builder.Services.AddConfigKit<ApiConfig>("Api")
.NotEmpty(c => c.BaseUrl, "API URL is required")
.UrlReachable(c => c.BaseUrl, message: "API is not reachable")
.InRange(c => c.TimeoutSeconds, 5, 300, "Timeout must be between 5 and 300 seconds")
.ValidateOnStartup();
Seamlessly integrates with Microsoft.Extensions.Options:
public class MyService
{
private readonly ApiConfig config;
public MyService(IOptions<ApiConfig> config)
{
this.config = config.Value; // Already validated at startup!
}
}
builder.Services.AddConfigKit<AppConfig>("App")
.NotEmpty(c => c.Name, "Application name is required")
.NotNull(c => c.Version, "Version is required")
.MatchesPattern(c => c.Version, @"^\d+\.\d+\.\d+$", "Version must be X.Y.Z format")
.ValidateOnStartup();
All comparison methods support any IComparable<T> type (int, decimal, DateTime, TimeSpan, etc.):
Integer validation:
builder.Services.AddConfigKit<ApiConfig>("Api")
.GreaterThan(c => c.Port, 1024, "Port must be > 1024")
.Maximum(c => c.Port, 65535, "Port must be <= 65535")
.InRange(c => c.MaxRetries, 0, 10, "Max retries must be 0-10")
.ValidateOnStartup();
Decimal validation:
builder.Services.AddConfigKit<ProductConfig>("Product")
.Minimum(c => c.Price, 0.01m, "Price must be at least $0.01")
.Maximum(c => c.Discount, 0.5m, "Discount cannot exceed 50%")
.ValidateOnStartup();
DateTime validation:
builder.Services.AddConfigKit<CampaignConfig>("Campaign")
.Minimum(c => c.StartDate, DateTime.Today, "Campaign must start today or later")
.LessThan(c => c.EndDate, new DateTime(2025, 12, 31), "Campaign must end before 2025")
.ValidateOnStartup();
TimeSpan validation:
builder.Services.AddConfigKit<ApiConfig>("Api")
.GreaterThan(c => c.Timeout, TimeSpan.Zero, "Timeout must be positive")
.Maximum(c => c.Timeout, TimeSpan.FromMinutes(5), "Timeout cannot exceed 5 minutes")
.ValidateOnStartup();
Available methods:
GreaterThan(selector, min) - Exclusive boundary (>)LessThan(selector, max) - Exclusive boundary (<)Minimum(selector, min) - Inclusive boundary (>=)Maximum(selector, max) - Inclusive boundary (<=)InRange(selector, min, max) - Inclusive boundaries (>=, <=)builder.Services.AddConfigKit<LoggingConfig>("Logging")
.NotEmpty(c => c.LogDirectory, "Log directory is required")
.DirectoryExists(c => c.LogDirectory, message: "Log directory does not exist")
.ValidateOnStartup();
builder.Services.AddConfigKit<ExternalApiConfig>("ExternalApi")
.NotEmpty(c => c.BaseUrl, "API URL is required")
.UrlReachable(c => c.BaseUrl, timeout: TimeSpan.FromSeconds(10), message: "API is not reachable")
.ValidateOnStartup();
builder.Services.AddConfigKit<SecurityConfig>("Security")
.NotEmpty(c => c.ApiKey, "API key is required")
.NoPlainTextSecrets(c => c.ApiKey, "API key appears to be a plain-text secret")
.ValidateOnStartup();
public sealed class DatabaseConfig
{
public string ConnectionString { get; set; } = string.Empty;
public int MaxPoolSize { get; set; }
public int CommandTimeoutSeconds { get; set; }
}
builder.Services.AddConfigKit<DatabaseConfig>("Database")
.NotEmpty(c => c.ConnectionString, "Connection string is required")
.InRange(c => c.MaxPoolSize, 1, 1000, "Max pool size must be between 1 and 1000")
.InRange(c => c.CommandTimeoutSeconds, 1, 600, "Timeout must be between 1 and 600 seconds")
.ValidateOnStartup();
Apply validation rules based on environment or configuration values:
builder.Services.AddConfigKit<SecurityConfig>("Security")
.NotEmpty(c => c.Environment, "Environment is required")
.When(c => c.Environment == "Production", b =>
{
// Rules only apply in production
b.NotEmpty(c => c.CertificatePath, "Certificate is required in production")
.FileExists(c => c.CertificatePath, message: "Certificate file not found");
})
.ValidateOnStartup();
builder.Services.AddConfigKit<DatabaseConfig>("Database")
.NotEmpty(c => c.ConnectionString, "Connection string is required")
.When(c => c.RequireSsl, b =>
{
b.MatchesPattern(c => c.ConnectionString, "Encrypt=True|Encrypt=true",
"SSL required but connection string does not specify Encrypt=True");
})
.ValidateOnStartup();
IOptions<T> configuration classesFor comprehensive documentation, advanced scenarios, and API reference, see the GitHub repository.
This project is licensed under the MIT License - see the LICENSE.txt file for details.
Károly Akácz
Inspired by the following concepts: