Skip to content

Getting Started with Pragmatic.Configuration

This guide covers the two pillars of Pragmatic.Configuration: compile-time binding via the source generator and runtime services for stores, caching, and resolution.

  • .NET 10.0+
  • Pragmatic.Abstractions (provides IConfigurationStore, ISecretStore, ICacheStack, EnvironmentProfile, ConfigurationChange)
  • Pragmatic.SourceGenerator analyzer reference (for [Configuration] attribute)
Terminal window
dotnet add package Pragmatic.Configuration

For the source generator:

<ProjectReference Include="..\Pragmatic.SourceGenerator\...\Pragmatic.SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />

Annotate a partial class with [Configuration]. The source generator produces IOptions<T> binding, DataAnnotation validation, and DI registration.

using Pragmatic.Configuration;
using System.ComponentModel.DataAnnotations;
[Configuration]
public partial class BookingOptions
{
[Required]
[MaxLength(100)]
public string HotelName { get; set; } = "";
[Range(1, 100)]
public int MaxGuests { get; set; } = 10;
public int CancellationWindowHours { get; set; } = 24;
}

The class must be partial. It cannot be static or abstract.

PropertyDefaultDescription
SectionPathInferred from class nameConfiguration section path (e.g., "Services:OrderApi")
ValidateOnStarttrueGenerates .ValidateOnStart() for immediate startup failure on invalid config

When SectionPath is omitted, it is inferred by removing the Options suffix from the class name:

Class NameInferred Section
BookingOptions"Booking"
PaymentOptions"Payment"
MyConfig"MyConfig" (no suffix to remove)

Override explicitly when needed:

[Configuration(SectionPath = "Services:OrderApi")]
public partial class OrderApiOptions { ... }

In appsettings.json:

{
"Booking": {
"HotelName": "Grand Hotel",
"MaxGuests": 50,
"CancellationWindowHours": 48
}
}

The source generator produces two extension methods:

  1. Per-type: AddBookingOptions(services, configuration) — binds a single options class.
  2. Per-assembly: Add{Prefix}Configuration(services, configuration) — calls all individual Add*Options methods in the assembly.
[StartupStep]
public class AppStartupStep : IStartupStep
{
public void ConfigureServices(
IServiceCollection services,
IConfiguration configuration,
IHostEnvironment environment)
{
// One line registers ALL [Configuration] options in the assembly
services.AddMyAppConfiguration(configuration);
}
}

The generated code wires:

services.AddOptions<BookingOptions>()
.Bind(configuration.GetSection("Booking"))
.ValidateDataAnnotations()
.ValidateOnStart();

Use standard Microsoft IOptions<T> patterns — the generator handles all the wiring:

// Real-time values (recommended for long-lived services)
public class BookingService(IOptionsMonitor<BookingOptions> options)
{
public int MaxGuests => options.CurrentValue.MaxGuests;
}
// Per-request snapshot
public class BookingHandler(IOptionsSnapshot<BookingOptions> options) { }
// Singleton (reads once at startup)
public class StaticService(IOptions<BookingOptions> options) { }

The source generator recognizes these System.ComponentModel.DataAnnotations attributes for generating .ValidateDataAnnotations():

  • [Required]
  • [Range]
  • [MaxLength], [MinLength], [StringLength]
  • [RegularExpression]
  • [EmailAddress], [Phone], [Url]

Properties with required keyword are treated as required even without [Required].

Step 6: Register Runtime Stores (Optional)

Section titled “Step 6: Register Runtime Stores (Optional)”

For runtime configuration (dynamic values, hot-reload, tenant overrides), register the configuration system:

services.AddPragmaticConfiguration(options =>
{
options.EnvironmentTag = "eu-west"; // sub-environment tag
options.MultiTenant.Enabled = true; // enable tenant-scoped overrides
options.MultiTenant.FallbackToBase = true; // cascade to base if no tenant value
options.Cache.DefaultTtl = TimeSpan.FromSeconds(30);
options.Cache.SecretsTtl = TimeSpan.FromMinutes(5);
});

This registers:

  • IConfigurationStore (default: InMemoryConfigurationStore)
  • ISecretStore (default: InMemorySecretStore)
  • ICacheStack (default: internal InMemoryConfigurationCacheStack, uses CacheCategories.Configuration)
  • EnvironmentProfile (resolved from IHostEnvironment)
  • ConfigurationResolver (scoped, cascade resolution with optional tenant context)

The in-memory stores are registered via TryAdd, so database or Azure backends replace them by registering first.

IDSeverityDescription
PRAG2000Error[Configuration] class must be declared as partial
PRAG2001Error[Configuration] class cannot be static or abstract
PRAG2050WarningProperty has [Required] but also has a default value
  • Stores — IConfigurationStore, ISecretStore, runtime reconfiguration, hot-reload