Previous Razor Pages vs MVC vs Minimal APIs in ASP.NET Core REST principles and API Design Next

The Options Pattern in .NET Core

The Options Pattern in .NET Core

The Options Pattern in .NET Core is a powerful way to manage configuration settings in a clean, type-safe, and scalable manner. It’s especially useful when dealing with grouped settings like connection strings, authentication parameters, or feature flags.

🧩 What Is the Options Pattern?

Instead of accessing configuration values directly using IConfiguration["Key"], the Options Pattern binds configuration sections to strongly typed classes and injects them using IOptions<T>.

🧪 Example: Using the Options Pattern

Step 1: Define a Configuration Class

public class JwtConfigSettings
{
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public bool ValidateExpiration { get; set; }
    public string SignInKey { get; set; }
}

Step 2: Add Settings to appsettings.json

"JwtConfigSettings": {
  "Issuer": "MyApp",
  "Audience": "MyUsers",
  "ValidateExpiration": true,
  "SignInKey": "SuperSecretKey123"
}

Step 3: Register in Program.cs

builder.Services.Configure<JwtConfigSettings>(
    builder.Configuration.GetSection("JwtConfigSettings"));

Step 4: Inject and Use in a Service or Controller

public class AuthService
{
    private readonly JwtConfigSettings _settings;

    public AuthService(IOptions<JwtConfigSettings> options)
    {
        _settings = options.Value;
    }

    public void PrintIssuer() => Console.WriteLine(_settings.Issuer);
}

✅ Advantages

  • Type Safety: Avoids magic strings and runtime errors.
  • Encapsulation: Keeps related settings together.
  • Testability: Easy to mock and inject during unit testing.
  • Separation of Concerns: Each component only accesses the settings it needs.
  • Validation Support: You can validate options during startup.

🧠 Best Practices

  • Use IOptions<T> for static configuration.
  • Use IOptionsSnapshot<T> for per-request scoped updates (e.g., in web apps).
  • Use IOptionsMonitor<T> for real-time updates and change notifications.
  • Validate options using ValidateDataAnnotations() or custom logic.
  • Keep configuration classes simple and focused.

🚫 When Not to Use It

  • For single values or rarely used settings, direct access via IConfiguration may be simpler.
  • If settings are dynamic and frequently updated, consider using a database or external config service.
  • Avoid using it for deeply nested or overly complex structures—it can become hard to manage.

⚠️ Precautions

  • Ensure property names in your class match the JSON keys exactly.
  • Avoid binding to fields—only public properties are supported.
  • Don’t mix IOptions<T> with manual configuration access—it breaks consistency.
  • Be cautious with IOptionsSnapshot<T> in singleton services—it won’t behave as expected.

🧭 Summary Table

Feature Use It When... Avoid It When...
IOptions<T> Static config, injected once You need frequent updates
IOptionsSnapshot<T> Per-request scoped config In singleton services
IOptionsMonitor<T> Real-time config changes You don’t need change tracking
Direct IConfiguration One-off or simple values You want type safety and structure

Advanced Usage of the Options Pattern in .NET Core

Let’s explore the advanced usage of the Options Pattern in .NET Core, including validation, change tracking, and best practices for real-world scenarios.

✅ 1. Validating Options

You can validate configuration at startup using data annotations or custom logic.

🔹 Using Data Annotations

public class EmailSettings
{
    [Required]
    public string SmtpServer { get; set; }

    [Range(1, 65535)]
    public int Port { get; set; }
}

Register with validation:

builder.Services
    .AddOptions<EmailSettings>()
    .Bind(builder.Configuration.GetSection("EmailSettings"))
    .ValidateDataAnnotations()
    .ValidateOnStart(); // optional: fail fast on startup

🔹 Custom Validation

builder.Services
    .AddOptions<EmailSettings>()
    .Bind(builder.Configuration.GetSection("EmailSettings"))
    .Validate(settings => settings.Port != 0, "Port must be non-zero");

🔄 2. Real-Time Updates with IOptionsMonitor<T>

Use IOptionsMonitor<T> when config values might change during runtime (e.g., from external sources like Azure App Configuration).

public class MyService
{
    private readonly IOptionsMonitor<EmailSettings> _monitor;

    public MyService(IOptionsMonitor<EmailSettings> monitor)
    {
        _monitor = monitor;
        _monitor.OnChange(updated => Console.WriteLine($"New SMTP: {updated.SmtpServer}"));
    }

    public void SendEmail()
    {
        var settings = _monitor.CurrentValue;
        // use settings
    }
}

🧠 When to Use the Options Pattern

Scenario Use It? Why?
Grouped config values (e.g., SMTP) Clean structure and type safety
Frequently changing config Use IOptionsMonitor<T> for updates
Per-request scoped config Use IOptionsSnapshot<T> in web apps
One-off values (e.g., version string) Use IConfiguration directly
Deeply nested or dynamic config Consider direct access or custom binding

⚠️ Precautions

  • Avoid using IOptionsSnapshot<T> in singleton services—it’s scoped to requests.
  • Always validate critical settings to avoid runtime failures.
  • Don’t mix IOptions<T> with direct IConfiguration access—it breaks consistency.
  • Use ValidateOnStart() in production to catch misconfigurations early.

🏆 Advantages Recap

  • Type-safe access to config
  • Centralized binding and validation
  • Supports change tracking
  • Clean separation of concerns
  • Easy to test and mock
Back to Index
Previous Razor Pages vs MVC vs Minimal APIs in ASP.NET Core REST principles and API Design Next
*