Learn C# - Configuration & Environment Management
Series/Learn C#/Episode 8
Episode 8 of 23

Learn C# - Configuration & Environment Management

This episode covers .NET application settings: appsettings.json and environment variables, the options pattern with binding to POCOs, secrets management and multi-environment configuration, and the role of IHostEnvironment in the .NET Generic Host.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

Production applications almost never run with hardcoded values. Database connection strings differ between development, staging, and production — and changing them must not mean recompiling the code. This is where configuration comes in.

The .NET configuration system is a layered model: values can come from JSON, environment variables, or the command line, and they are merged in a specific order. The most specific value wins.

Episode 8 covers appsettings.json, environment variables, the options pattern, secrets management, and the Generic Host that underpins all modern ASP.NET Core applications.

appsettings.json and Environment Variables

Configuration Hierarchy

The appsettings.json file stores default configuration. When the application runs, the environment variables provider overrides its values — a pattern that's very useful in containers:

Isi appsettings.json
{
  "ConnectionStrings": {
    "Default": "Host=localhost;Database=toko;Username=postgres;Password=secret"
  },
  "Email": {
    "Sender": "no-reply@contoh.id",
    "SmtpPort": 587
  },
  "AllowedHosts": "*"
}

The configuration above is read automatically when the application starts. To override Email:SmtpPort from an environment variable, set Email__SmtpPort=465 — the double __ separator in environment variables represents the colon in the JSON hierarchy.

Options Pattern and Binding to POCO

Creating an Options Class

Capturing configuration directly into a POCO makes your code resistant to typos in key names. This is the options pattern:

Options class and registration
class EmailOptions
{
    public string Sender { get; set; } = "";
    public int SmtpPort { get; set; }
}
 
var builder = Host.CreateApplicationBuilder(args);
builder.Services.Configure<EmailOptions>(
    builder.Configuration.GetSection("Email"));
 
var app = builder.Build();
app.Run();

GetSection("Email") reads the Email subtree from JSON and maps it to the class properties. Now you can read configuration with a safe type instead of a raw string.

Accessing Options in Services

Options are accessed by injecting IOptions<T> into a class:

Consuming options
class EmailSender
{
    private readonly EmailOptions _options;
 
    public EmailSender(IOptions<EmailOptions> options)
    {
        _options = options.Value;
    }
 
    public void Kirim()
    {
        Console.WriteLine($"Mengirim dari {_options.Sender} port {_options.SmtpPort}");
    }
}

This pattern makes settings easy to test — you just replace IOptions<EmailOptions> with a different value during testing.

Secrets Management and Multi-environment

appsettings.Development.json and user-secrets

.NET has a built-in concept of environments: Development, Staging, and Production. The application loads appsettings.json, then overrides it with appsettings.{Environment}.json. For local secrets, use user-secrets, which are stored outside the project:

Managing local secrets
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Default" "Host=localhost;Database=toko;Username=postgres;Password=rahasia"

The dotnet user-secrets set command stores the value in the user profile, not in the repository. This secrets.json file is never committed, so passwords don't leak into Git.

Determining the Active Environment

The environment is selected through the ASPNETCORE_ENVIRONMENT variable:

Running with a specific environment
ASPNETCORE_ENVIRONMENT=Production dotnet run

The command above loads the appsettings.Production.json file if present. Environment names can be checked in code through IHostEnvironment.

IHostEnvironment and the Generic Host

Using the .NET Generic Host

The Generic Host unifies configuration, logging, dependency injection, and the application lifecycle in one pipeline. When you need an environment value inside the code:

Reading the environment from the host
var builder = Host.CreateApplicationBuilder(args);
var environment = builder.Environment;
 
if (environment.IsDevelopment())
{
    builder.Services.AddDeveloperExceptionPage();
}
 
Console.WriteLine($"Environment: {environment.EnvironmentName}");
Console.WriteLine($"Root path: {environment.ContentRootPath}");

IHostEnvironment.EnvironmentName returns the active environment name, and helpers like IsDevelopment() make conditional code clear. This also lets us add different configuration for each environment.

Closing

Key takeaways:

  • Configuration is merged from JSON, environment variables, and the command line.
  • The options pattern maps configuration into type-safe POCOs.
  • ASPNETCORE_ENVIRONMENT selects the per-environment configuration file.
  • user-secrets keeps local secrets out of the repository.
  • The Generic Host unifies configuration, DI, logging, and lifecycle.

In the next episode 9 we touch databases: data access and persistence — Entity Framework Core and DbContext, CRUD operations, migrations and relationships, querying with LINQ and raw SQL, and database providers such as SQL Server, PostgreSQL, and SQLite.

Learn C# - Configuration & Environment Management | Learn C#