How to Use Options Pattern & Secret Manager in .NET

How to Handle Configuration via Options Pattern & Secret Managers

Are your secrets safe in your .NET app, or are you still hardcoding passwords like it’s 2005?
Let’s talk about a cleaner, safer, and more scalable way to manage configuration using the Options Pattern—and keep your sensitive data out of source control with Secret Manager.

Understanding the Configuration Landscape in .NET

What Is Application Configuration?

In a .NET application, configuration is how your app understands its environment—what database to connect to, which API to use, how verbose the logging should be. You typically configure your app using:

  • appsettings.json — the default file for storing app configuration.
  • Environment variables — ideal for cloud-based deployments.
  • Command-line arguments — for overrides during deployment.
  • User secrets — for development-only secrets.
  • Azure App Configuration or Key Vault — for enterprise-grade scenarios.

Challenges of Hard-Coded or Static Configuration

Hardcoding config values like API keys or DB passwords is tempting—but dangerous. Here’s why:

  • Security Risks: Secrets in code can be pushed to GitHub by mistake.
  • Lack of Flexibility: Changing a config value means rebuilding and redeploying.
  • Testing Nightmare: It’s hard to mock a DB connection string hardcoded in a class.

Let’s avoid that mess. Instead, we’ll use the Options Pattern.

The Options Pattern Explained

What Is the Options Pattern?

The Options Pattern lets you decouple your configuration from business logic by binding configuration sections to strongly typed classes. You inject these classes using IOptions<T> so they’re testable, clean, and maintainable.

Implementing the Options Pattern

Let’s say we want to configure email settings.

appsettings.json:

"EmailSettings": {
  "SmtpServer": "smtp.mailtrap.io",
  "Port": 2525,
  "UseSSL": true
}

EmailSettings.cs:

public class EmailSettings
{
    public string SmtpServer { get; set; }
    public int Port { get; set; }
    public bool UseSSL { get; set; }
}

Program.cs:

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

EmailService.cs:

public class EmailService
{
    private readonly EmailSettings _settings;

    public EmailService(IOptions<EmailSettings> options)
    {
        _settings = options.Value;
    }
}

This structure is clean, testable, and keeps logic and config separate.

IOptions vs IOptionsSnapshot vs IOptionsMonitor

  • IOptions<T>: Singleton, reads config once at startup.
  • IOptionsSnapshot<T>: Scoped, reads config per request—great for web apps.
  • IOptionsMonitor<T>: Supports runtime config change notifications.

Testing with the Options Pattern

Mocking IOptions<T> is straightforward:

var mockOptions = Options.Create(new EmailSettings {
    SmtpServer = "mock.server",
    Port = 1234,
    UseSSL = false
});

Use this in your test setup to avoid reading from appsettings.json during unit tests.

Managing Sensitive Data with Secret Managers

What Is the Secret Manager Tool?

Secret Manager is a tool in .NET for storing development secrets outside the codebase. It keeps them in a JSON file under your user profile.

Setting Up and Using the Secret Manager

  1. Initialize secrets:
dotnet user-secrets init
  1. Add secrets:
dotnet user-secrets set "EmailSettings:SmtpServer" "smtp.secrethost.com"
  1. Read secrets: Your appsettings.json structure remains the same.

Best Practices for Secret Management

  • Prefix keys with section names (e.g., EmailSettings:) for clarity.
  • Keep secrets out of appsettings.*.json.
  • Use Azure Key Vault for production.

Combining Options Pattern with Secret Managers

Accessing Secrets via Options Pattern

The beauty here? It’s seamless. If your secret keys match your config class structure, IOptions<T> will bind them automatically.

Real-World Example

Storing a database connection string securely:

dotnet user-secrets set "DbSettings:ConnectionString" "Server=...;User Id=...;Password=...;"

DbSettings.cs:

public class DbSettings {
    public string ConnectionString { get; set; }
}

Startup:

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

This setup works just like regular appsettings.json but pulls from a safer source in dev.

Scaling Up: Alternatives and Enterprise Considerations

Azure Key Vault and Beyond

Use Azure Key Vault when:

  • Secrets are needed in production.
  • You need fine-grained access control.
  • You want secrets to rotate automatically.

Configuration in CI/CD Pipelines

In pipelines (like GitHub Actions or Azure DevOps):

  • Use secure environment variables.
  • Inject secrets at runtime using Key Vault or pipeline secrets.
  • Avoid storing secrets in any source-controlled file.

FAQ: Quick Answers to Common Questions

Can I combine multiple configuration sources?

Yes, .NET merges them in a defined order (JSON < Environment Vars < Secrets).

Should I use IOptionsMonitor everywhere?

Only if you need runtime updates. Most apps are fine with IOptions.

Are secrets stored by Secret Manager encrypted?

Yes, they are stored locally in a protected user profile location.

Conclusion: Secure, Scalable, and Testable Configuration

The Options Pattern and Secret Manager combo is the upgrade your app deserves. You keep your code clean, your secrets safe, and your app flexible enough to handle any environment. Next time you write a config-dependent class—don’t hardcode, option-ize it.

Ready to refactor your config strategy? Leave a comment below or check out our other architecture tips!

Leave a Reply

Your email address will not be published. Required fields are marked *