Skip to content

A C# source generator that automatically configures IOptions for classes marked with a custom attribute. Also generates an appsettings.schema.json for improved appsettings DX.

Notifications You must be signed in to change notification settings

XeNz/OptionsSourceGenerated

Repository files navigation

Options Source Generator

A C# source generator that automatically configures IOptions for classes marked with a custom attribute.

Features

  • Automatically generates IServiceCollection extension methods to configure options
  • Supports validation at startup via ValidateOnStart property
  • Supports data annotations validation via ValidateDataAnnotations property
  • Configurable configuration section binding
  • Automatic JSON schema generation for IDE intellisense and validation in appsettings.json
  • Zero runtime overhead - all code is generated at compile time

Project Structure

  • OptionsSourceGenerated: Example console application demonstrating usage
  • OptionsSourceGenerated.Generator: The source generator project
  • OptionsSourceGenerated.Generator.Tests: Comprehensive unit tests for the source generator

Usage

1. Mark your options class with the [ConfigureOptions] attribute

using System.ComponentModel.DataAnnotations;

[ConfigureOptions(ConfigurationSection = "Database", ValidateOnStart = true, ValidateDataAnnotations = true)]
public class DatabaseOptions
{
    [Required(ErrorMessage = "Connection string is required")]
    [MinLength(10, ErrorMessage = "Connection string must be at least 10 characters")]
    public string ConnectionString { get; set; } = string.Empty;

    [Range(0, 10, ErrorMessage = "Max retries must be between 0 and 10")]
    public int MaxRetries { get; set; }

    [Range(1, 300, ErrorMessage = "Timeout must be between 1 and 300 seconds")]
    public int TimeoutSeconds { get; set; }
}

[ConfigureOptions(ConfigurationSection = "Api", ValidateOnStart = false, ValidateDataAnnotations = true)]
public class ApiOptions
{
    [Required(ErrorMessage = "Base URL is required")]
    [Url(ErrorMessage = "Base URL must be a valid URL")]
    public string BaseUrl { get; set; } = string.Empty;

    [Required(ErrorMessage = "API key is required")]
    [MinLength(5, ErrorMessage = "API key must be at least 5 characters")]
    public string ApiKey { get; set; } = string.Empty;

    [Range(1, 1000, ErrorMessage = "Rate limit must be between 1 and 1000")]
    public int RateLimitPerMinute { get; set; }
}

2. Configure appsettings.json

{
  "Database": {
    "ConnectionString": "Server=localhost;Database=MyDb;",
    "MaxRetries": 3,
    "TimeoutSeconds": 30
  },
  "Api": {
    "BaseUrl": "https://api.example.com",
    "ApiKey": "your-api-key-here",
    "RateLimitPerMinute": 60
  }
}

3. Register options in your application

var builder = Host.CreateApplicationBuilder(args);

// The source generator creates the AddGeneratedOptions extension method
builder.Services.AddGeneratedOptions(builder.Configuration);

var host = builder.Build();

4. Inject and use the options

// Constructor injection
public class MyService
{
    private readonly DatabaseOptions _dbOptions;

    public MyService(IOptions<DatabaseOptions> dbOptions)
    {
        _dbOptions = dbOptions.Value;
    }
}

// Or direct retrieval
var databaseOptions = host.Services.GetRequiredService<IOptions<DatabaseOptions>>().Value;

Generated Code

The source generator automatically creates an extension method like this:

public static class OptionsConfigurationExtensions
{
    public static IServiceCollection AddGeneratedOptions(this IServiceCollection services, IConfiguration configuration)
    {
        // Configure DatabaseOptions
        services.AddOptions<OptionsSourceGenerated.DatabaseOptions>()
            .Bind(configuration.GetSection("Database"))
            .ValidateDataAnnotations()
            .ValidateOnStart();

        // Configure ApiOptions
        services.AddOptions<OptionsSourceGenerated.ApiOptions>()
            .Bind(configuration.GetSection("Api"))
            .ValidateDataAnnotations();

        return services;
    }
}

Attribute Properties

ConfigurationSection (required)

The configuration section name to bind from appsettings.json. Supports nested sections using colon notation (e.g., "Parent:Child").

ValidateOnStart (optional, default: false)

When set to true, validates the options configuration at application startup. This uses the .ValidateOnStart() extension method, ensuring configuration errors are caught early.

ValidateDataAnnotations (optional, default: false)

When set to true, enables data annotations validation for the options class. This uses the .ValidateDataAnnotations() extension method, which validates properties decorated with attributes from System.ComponentModel.DataAnnotations (e.g., [Required], [Range], [MinLength], [Url], etc.).

JSON Schema Generation

The source generator automatically creates a JSON schema for all your options classes. This schema can be used for:

  • IDE Intellisense: Get autocomplete and validation in appsettings.json
  • Build-time validation: Catch configuration errors before runtime
  • Documentation: Understand configuration structure and constraints

Example Generated Schema

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Application Settings",
  "type": "object",
  "properties": {
    "Database": {
      "type": "object",
      "required": ["ConnectionString"],
      "properties": {
        "ConnectionString": {
          "type": "string",
          "minLength": 10
        },
        "MaxRetries": {
          "type": "integer",
          "minimum": 0,
          "maximum": 10
        },
        "TimeoutSeconds": {
          "type": "integer",
          "minimum": 1,
          "maximum": 300
        }
      }
    }
  }
}

Accessing the Schema

The generated schema is available through the OptionsJsonSchema.Schema constant:

// Export schema to a file
File.WriteAllText("appsettings.schema.json", OptionsJsonSchema.Schema);

// Or use the helper
SchemaExporter.ExportSchema("appsettings.schema.json");

// Print to console
Console.WriteLine(OptionsJsonSchema.Schema);

Automatic Schema Generation During Build

The schema is automatically generated during build and placed in your project directory next to appsettings.json. The post-build event runs:

dotnet run --no-build -- --export-schema-only

This creates/updates appsettings.schema.json in your project root.

Using the Schema in Your IDE

Rider / IntelliJ IDEA

  1. Add a $schema property to your appsettings.json:
{
  "$schema": "./appsettings.schema.json",
  "Database": {
    "ConnectionString": "Server=localhost;Database=MyDb;",
    "MaxRetries": 3,
    "TimeoutSeconds": 30
  }
}
  1. Rider will automatically detect the schema and provide:

    • Code completion (Ctrl+Space) for property names
    • Type validation with error highlighting
    • Quick documentation (Ctrl+Q / Cmd+J) on hover
    • Constraint validation for min/max values, string lengths, formats
  2. Additional Rider Setup (if schema isn't detected):

    • Go to Settings → Languages & Frameworks → Schemas and DTDs → JSON Schema Mappings
    • Click + to add a new mapping
    • Schema file: appsettings.schema.json (in your project directory)
    • Schema version: JSON Schema version 7
    • Add file pattern: appsettings.json or appsettings.*.json

VS Code

  1. Add $schema to your appsettings.json (same as above)
  2. VS Code automatically supports JSON schemas via the $schema property
  3. You'll get IntelliSense, validation, and hover information

Visual Studio

  1. Add $schema to your appsettings.json (same as above)
  2. Visual Studio 2019+ automatically supports JSON schemas
  3. Rebuild your project to regenerate the schema after changes

The IDE will now provide:

  • Intellisense for property names
  • Validation of property types
  • Constraint validation (minLength, minimum, maximum, format)
  • Hover documentation for properties
  • Real-time error detection

Generated Schema Features

The schema includes:

  • PascalCase property names (matching C# conventions)
  • Required properties: Properties with [Required] attribute are listed in the required array
  • Property types: string, integer, number, boolean, array, object
  • Data annotation constraints:
    • [Required] → Added to required array
    • [Range(min, max)]minimum and maximum
    • [MinLength(n)]minLength
    • [MaxLength(n)]maxLength
    • [Url]format: "uri"
    • [EmailAddress]format: "email"
  • XML documentation comments as descriptions
  • Nested configuration section support (e.g., "Parent:Child")
  • Clean JSON formatting with proper indentation

Note: Property names use PascalCase to match C# naming conventions and .NET configuration binding.

Building

dotnet build

Running the Example

dotnet run --project OptionsSourceGenerated/OptionsSourceGenerated.csproj

Running Tests

The project includes comprehensive unit tests for the source generator:

dotnet test OptionsSourceGenerated.Generator.Tests/OptionsSourceGenerated.Generator.Tests.csproj

Test coverage includes:

  • Attribute code generation
  • Extension method generation with various validation combinations
  • Multiple classes with different configurations
  • Nested configuration sections
  • Edge cases (missing configuration sections, special characters, Unicode, etc.)
  • Partial classes, record classes, and nested classes

How It Works

  1. The source generator scans for classes decorated with [ConfigureOptions]
  2. At compile time, it generates an extension method that registers all marked options classes
  3. The generated code uses:
    • services.Configure<T>() for simple binding (when no validation is enabled)
    • services.AddOptions<T>().Bind().ValidateDataAnnotations() when data annotations validation is enabled
    • services.AddOptions<T>().Bind().ValidateDataAnnotations().ValidateOnStart() when both validations are enabled
  4. No reflection or runtime discovery - everything is resolved at compile time

Requirements

  • .NET 10.0 or later (can be adjusted in the project files)
  • Microsoft.Extensions.Hosting 9.0.0+
  • Microsoft.Extensions.Options.ConfigurationExtensions 9.0.0+
  • Microsoft.Extensions.Options.DataAnnotations 9.0.0+ (required when using ValidateDataAnnotations)

About

A C# source generator that automatically configures IOptions for classes marked with a custom attribute. Also generates an appsettings.schema.json for improved appsettings DX.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages