Skip to content

TimeWarpEngineering/timewarp-options-validation

Repository files navigation

Dotnet Stars Discord workflow nuget nuget Issues Open Forks License Twitter

Twitter Twitter

TimeWarp.OptionsValidation

TimeWarp Logo

TimeWarp.OptionsValidation integrates FluentValidation with Microsoft.Extensions.Options to provide automatic validation of your configuration settings at application startup.

Why Use This Library?

Configuration errors are a common source of runtime failures. TimeWarp.OptionsValidation helps you fail fast by validating all configuration settings when your application starts, rather than discovering errors when the configuration is first accessed (which could be hours or days later in production).

Key Benefits:

  • Validates configuration settings using FluentValidation rules
  • Integrates seamlessly with Microsoft.Extensions.Options
  • Catches configuration errors at startup, not at runtime
  • Provides clear, actionable error messages
  • Supports both IConfiguration binding and programmatic configuration

Give a Star! ⭐

If you like or are using this project please give it a star. Thank you!

Installation

dotnet add package TimeWarp.OptionsValidation

You can see the latest NuGet packages from the official TimeWarp NuGet page.

Usage

Basic Setup with Automatic Startup Validation

Use AddFluentValidatedOptions() which returns OptionsBuilder<T>, allowing you to chain with .ValidateOnStart() for automatic startup validation.

1. Define Your Options Class with Nested Validator

using FluentValidation;

public class DatabaseOptions
{
  public string ConnectionString { get; set; } = string.Empty;
  public int MaxRetries { get; set; }
  public int CommandTimeout { get; set; }

  // Nested validator - sealed and only used here
  public sealed class Validator : AbstractValidator<DatabaseOptions>
  {
    public Validator()
    {
      RuleFor(x => x.ConnectionString)
        .NotEmpty()
        .WithMessage("Database connection string is required");

      RuleFor(x => x.MaxRetries)
        .GreaterThan(0)
        .LessThanOrEqualTo(10)
        .WithMessage("MaxRetries must be between 1 and 10");

      RuleFor(x => x.CommandTimeout)
        .GreaterThanOrEqualTo(30)
        .WithMessage("CommandTimeout must be at least 30 seconds");
    }
  }
}

2. Register with Automatic Startup Validation

using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

// Register options with automatic startup validation
builder.Services
  .AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(builder.Configuration)
  .ValidateOnStart(); // ✅ Validates when host starts, throws on error

var app = builder.Build();
app.Run(); // Validation happens automatically before this runs

What this does:

  • Binds the DatabaseOptions section from appsettings.json
  • Registers the FluentValidation validator
  • Validates configuration at startup (before app.Run())
  • Fails fast with clear error messages if configuration is invalid
  • No manual validation calls needed!

Configuration Binding

The library automatically discovers which configuration section to bind based on simple, predictable rules.

Default: Class Name

By default, the library uses the class name as the configuration section name:

public class DatabaseOptions
{
  public string ConnectionString { get; set; } = string.Empty;
  // ...
}

Binds to "DatabaseOptions" section:

{
  "DatabaseOptions": {
    "ConnectionString": "Server=localhost;Database=myapp;",
    "MaxRetries": 3,
    "CommandTimeout": 30
  }
}
// Automatically binds to "DatabaseOptions" section
services
  .AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration)
  .ValidateOnStart();

Custom Configuration Key with [ConfigurationKey] Attribute

Override the default by decorating your options class with [ConfigurationKey]:

Simple Configuration Key:

using TimeWarp.OptionsValidation;

[ConfigurationKey("Database")]
public class DatabaseOptions
{
  public string ConnectionString { get; set; } = string.Empty;
  // ...
}

Binds to "Database" section:

{
  "Database": {
    "ConnectionString": "Server=localhost;Database=myapp;",
    "MaxRetries": 3,
    "CommandTimeout": 30
  }
}

Hierarchical Key with Colon Separator:

[ConfigurationKey("MyApp:Settings:Database")]
public class DatabaseOptions
{
  public string ConnectionString { get; set; } = string.Empty;
  // ...
}

Binds to nested "MyApp" → "Settings" → "Database" path:

{
  "MyApp": {
    "Settings": {
      "Database": {
        "ConnectionString": "Server=localhost;Database=myapp;",
        "MaxRetries": 3,
        "CommandTimeout": 30
      }
    }
  }
}
// Automatically binds to configuration key specified in attribute
services
  .AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration)
  .ValidateOnStart();

Advanced: Manual Section Binding

For dynamic section paths or complex scenarios not covered by the attribute:

// Manual binding for runtime-determined paths
string environment = builder.Environment.EnvironmentName;
services.AddOptions<DatabaseOptions>()
  .Bind(configuration.GetSection($"{environment}:Database"))
  .ValidateFluentValidation<DatabaseOptions, DatabaseOptions.Validator>()
  .ValidateOnStart();

Automatic Configuration Key Resolution Summary:

  • ✅ Uses class name: DatabaseOptions"DatabaseOptions"
  • ✅ Simple override: [ConfigurationKey("Database")]"Database"
  • ✅ Hierarchical paths: [ConfigurationKey("MyApp:Settings:Database")]"MyApp" → "Settings" → "Database"
  • ❌ Does NOT trim suffixes like "Options" automatically
  • ❌ Does NOT pluralize names automatically

Programmatic Configuration

You can also configure options programmatically without IConfiguration:

services
  .AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(options =>
  {
    options.ConnectionString = "Server=localhost;Database=myapp;";
    options.MaxRetries = 3;
    options.CommandTimeout = 30;
  })
  .ValidateOnStart();

Complete Startup Example

using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

// Register multiple validated options with automatic startup validation
builder.Services
  .AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(builder.Configuration)
  .ValidateOnStart();

builder.Services
  .AddFluentValidatedOptions<CacheOptions, CacheOptions.Validator>(builder.Configuration)
  .ValidateOnStart();

builder.Services
  .AddFluentValidatedOptions<EmailOptions, EmailOptions.Validator>(builder.Configuration)
  .ValidateOnStart();

var app = builder.Build();
app.Run(); // All options validated before this runs

If any configuration is invalid, the application will fail to start with clear error messages indicating exactly which settings are invalid and why.

Without Startup Validation

If you don't need automatic startup validation, simply omit .ValidateOnStart():

// Validates on first access instead of at startup
services.AddFluentValidatedOptions<DatabaseOptions, DatabaseOptions.Validator>(configuration);
// No .ValidateOnStart() call - validation happens lazily

This approach validates options when they're first accessed rather than at application startup.

Features

  • Automatic Startup Validation: Use .ValidateOnStart() to fail fast on invalid configuration
  • Automatic Key Discovery: Uses the class name as the configuration key by default
  • Custom Key Mapping: Use [ConfigurationKey] attribute to override the configuration key
  • Hierarchical Keys: Support for nested configuration paths using colon separators
  • Seamless Integration: Works with Microsoft.Extensions.Options infrastructure and OptionsBuilder<T>
  • FluentValidation Power: Rich validation rules, custom validators, conditional validation
  • Clear Error Messages: Detailed, actionable error messages from FluentValidation
  • Type Safety: Strongly-typed options with compile-time checking
  • Flexible API: Choose between fluent API (with .ValidateOnStart()) or simple registration

Releases

See the Release Notes

Unlicense

License

Contributing

Time is of the essence. Before developing a Pull Request I recommend opening a discussion.

Please feel free to make suggestions and help out with the documentation. Please refer to Markdown for how to write markdown files.

Contact

Sometimes the github notifications get lost in the shuffle. If you file an issue and don't get a response in a timely manner feel free to ping on our Discord server.

Discord

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages