Expert · Web APIs with ASP.NET Core and EF Core · topic 34 of 43 · 30 min
Configuration, secrets and the middleware pipeline
Explanation
Why configuration?
The same code runs on your laptop, a test server and the live server, but each needs different settings: database address, payment gateway keys, log level. Hard-coding them means changing code for every environment and leaking secrets. Configuration keeps settings outside the code.
appsettings.json
{
"Logging": { "LogLevel": { "Default": "Information" } },
"ConnectionStrings": {
"Shop": "Data Source=shop.db"
},
"Sms": {
"Sender": "SHOPIN",
"MaxPerMinute": 30
}
}string? sender = builder.Configuration["Sms:Sender"];
int max = builder.Configuration.GetValue<int>("Sms:MaxPerMinute");
string? conn = builder.Configuration.GetConnectionString("Shop"); // reads ConnectionStrings:ShopWhere values come from, and who wins
The default builder reads several sources. If the same key appears in more than one, the source read later wins.
- appsettings.json
- appsettings.{Environment}.json, for example appsettings.Development.json
- User secrets (only in the Development environment)
- Environment variables
- Command-line arguments
The environment name comes from the ASPNETCORE_ENVIRONMENT variable: usually Development, Staging or Production. If it is not set, it is Production. Locally, Properties/launchSettings.json sets it to Development for dotnet run.
# Linux / macOS
export ConnectionStrings__Shop="Host=db;Database=shop;Username=app;Password=..."
# Windows PowerShell
$env:ConnectionStrings__Shop = "..."Secrets in development: user-secrets
dotnet user-secrets init
dotnet user-secrets set "Sms:ApiKey" "test-key-from-provider-dashboard"
dotnet user-secrets listThe options pattern: typed settings
builder.Services.Configure<SmsOptions>(builder.Configuration.GetSection("Sms"));
public class SmsOptions
{
public string Sender { get; set; } = "";
public int MaxPerMinute { get; set; }
public string ApiKey { get; set; } = "";
}
public class SmsSender(IOptions<SmsOptions> options)
{
private readonly SmsOptions _o = options.Value;
public string Describe() => $"{_o.Sender}, max {_o.MaxPerMinute}/min";
}Middleware: the request pipeline
Middleware is a component that sits in the path of every request. Each one can do something before passing the request to the next one, and something after the response comes back. Together they form the pipeline. The order you write them in Program.cs is the order they run.
var app = builder.Build();
if (!app.Environment.IsDevelopment())
app.UseExceptionHandler("/error"); // catch crashes, return a safe error
app.UseHttpsRedirection(); // send http:// to https://
app.UseAuthentication(); // who are you?
app.UseAuthorization(); // are you allowed?
app.Use(async (context, next) => // your own middleware
{
var watch = System.Diagnostics.Stopwatch.StartNew();
await next(context); // run the rest of the pipeline
Console.WriteLine($"{context.Request.Method} {context.Request.Path} -> {context.Response.StatusCode} in {watch.ElapsedMilliseconds} ms");
});
app.MapGet("/", () => "Hello");
app.Run();Common mistakes and fixes
- UseAuthorization before UseAuthentication: the user is not identified yet, so authorization fails. Authentication first.
- Committing appsettings.Development.json with real keys: move them to user-secrets and rotate the leaked keys.
- Typos in configuration keys: builder.Configuration["Sms:Sendr"] silently returns null. Prefer the options pattern.
- Writing to the response after calling next when the response has already started: causes an error. Log, do not modify, after next.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Configuration, secrets and the middleware pipeline · 1/7
In this lesson you will keep settings out of your code and understand how each request flows through middleware.
Prefer a video by a teacher? Find free videos on “ASP.NET Core configuration appsettings user secrets middleware pipeline” on YouTube ↗
In real life: A payment API that behaves differently in each environment
A fintech startup in Bengaluru runs the same payment API code in Development (fake gateway, detailed errors), Staging (sandbox gateway) and Production (live gateway, safe errors). Only configuration changes: appsettings files hold non-secret settings, user-secrets hold the developer's sandbox key, and production keys are environment variables set by the hosting platform.
// appsettings.json (committed): no secrets
// { "Payments": { "BaseUrl": "https://sandbox.example-gateway.com", "TimeoutSeconds": 10 } }
// production server environment variables (NOT in Git):
// Payments__BaseUrl=https://api.example-gateway.com
// Payments__ApiKey=<set in the hosting dashboard>
builder.Services.Configure<PaymentOptions>(builder.Configuration.GetSection("Payments"));
public class PaymentOptions
{
public string BaseUrl { get; set; } = "";
public string ApiKey { get; set; } = "";
public int TimeoutSeconds { get; set; } = 10;
}Practice (0/4 done)
The key Sms:Sender is "A" in appsettings.json and the environment variable Sms__Sender is "B". What value does the app see?
Where should a developer keep a payment sandbox API key on their laptop?
Which order is correct?
In a webapi project: add a "Shop" section to appsettings.json with Name and SupportPhone, bind it with the options pattern, and expose GET /info that returns them. Store a fake "Shop:ApiKey" with user-secrets and confirm it is readable but not in any file in your project folder. Add a middleware that adds a response header X-Elapsed-Ms with the time taken.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Entity Framework Core: databases with C#Web APIs with ASP.NET Core and EF Core · same module
- Routing, model binding, validation and status codesWeb APIs with ASP.NET Core and EF Core · same module
- Middleware: checks before your handlerBackend Development · Node.js and Express
- Keeping secrets safeBackend Development · Backend security
- Environment variables and secretsFull-Stack Development · Deploying and running the app
- Middleware: logging, recovery and request IDsGo Programming (backend and cloud) · Building a REST API with a database
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is the default environment if ASPNETCORE_ENVIRONMENT is not set?
Answer: Production
Unset means Production, the safe default.
In environment variable names, what replaces the colon of nested keys?
Answer: A double underscore __
ConnectionStrings__Shop maps to ConnectionStrings:Shop.
A secret key was committed to Git by mistake. What should you do?
Answer: Rotate the key (issue a new one and revoke the old one)
Assume it leaked; rotating is the only safe fix.
What happens if a middleware does not call next?
Answer: The request stops there (short-circuit) and later middleware and endpoints do not run
Short-circuiting is used for things like returning cached responses.
Are user secrets meant for production?
Answer: No, they are a development-only convenience and are not encrypted
Production should use environment variables or a vault.
Where should the error-handling middleware be added?Related
Answer: After all routes, at the end
Errors from routes travel down the chain, so the error handler must come after them.
Where should a production database password live?Related
Answer: In the hosting platform's environment settings or a secret manager
Environment settings and secret managers keep the value out of code and control who can read it.
From Keeping secrets safe
A session secret was pushed to a public GitHub repo. What fixes the problem?Related
Answer: Rotate: make a new secret, deploy it, and stop using the old one
The old value stays in git history and may already be copied. Only replacing it makes it useless.
Why wrap http.ResponseWriter in a logging middleware?Related
Answer: To remember the status code the handler wrote
ResponseWriter has no method to read back the status.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Web APIs with ASP.NET Core and EF Core” before its exam: Entity Framework Core: databases with C#.
- Module 8 exam: Web APIs with ASP.NET Core and EF Core · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Testing, tooling and Unity. 43 topics left in the course before the final exam.