Stop Hardcoding AppSettings: A Clean Pattern for Blazor Environment Configuration
If your Blazor app works locally but breaks in Azure,
there’s a good chance configuration is the real bug.
Blazor developers often treat configuration as a startup
concern—something you wire up once and forget.
That works…
until you add environments.
or feature flags.
or secrets.
or a second app.
Then the configuration quietly turns into a distributed
liability.
Let’s talk about why hardcoded AppSettings patterns don’t
scale—and a cleaner, environment-aware approach that actually survives real
projects.
The Common (But Fragile) Pattern
Most Blazor apps start like this:
// appsettings.json
{
"InCounter":
5,
"FeatureFlagStyleTable":
true
}
Var styleTable = configService.Value.FeatureFlagStyleTable;
At first, this feels fine.
Then you add:
- appsettings.Development.json
- appsettings.Test.json
- appsettings.Production.json
- Azure
App Service settings
- Feature
flags
- Secrets
in Key Vault
Suddenly:
- Keys
are duplicated
- Values
drift between environments
- Missing
settings cause runtime failures
- No one
knows which values are actually being used
And the worst part?
👉 The configuration is
invisible at runtime.
Why Hardcoded AppSettings Fail in Blazor
Hardcoding configuration values—or scattering string keys
across the codebase—creates several problems:
❌ No compile-time safety
❌
No validation on startup
❌
No single source of truth
❌
Environment logic leaks everywhere
❌
Impossible to reason about in production
Blazor doesn’t need more configuration magic.
It needs strong structure.
A Clean Pattern: Strongly-Typed, Validated Configuration
The goal is simple:
Load configuration once, validate it early, and inject it
everywhere.
Step 1: Define a Configuration Contract
public sealed class AppConfig
{
public required int
InCounter { get; init; }
public bool FeatureFlagStyleTable{
get; init; }
}
This class becomes the contract between your app and
its configuration.
No magic strings.
No guessing.
Step 2: Bind and Validate on Startup
builder.Services
.AddOptions<AppConfig>()
.Bind(builder.Configuration)
.ValidateDataAnnotations()
.ValidateOnStart();
Now your app will:
- Fail
fast if config is missing
- Catch
errors at startup—not at runtime
- Make
configuration explicit and testable
Step 3: Inject Configuration, Not IConfiguration
In Weather.razor
@using BetterConfigService
@using Microsoft.Extensions.Options
@inject IOptions<AppConfig> configService
protected override async Task OnInitializedAsync()
{
styleTable =
configService.Value.FeatureFlagStyleTable;
}
Your services now depend on intent, not
infrastructure.
Handling Multiple Environments (Without If Statements)
Blazor already supports layered configuration:
appsettings.json
appsettings.Development.json
appsettings.Production.json
The key insight:
Your code should never care which environment it’s
running in.
The environment only decides which values are loaded,
not how they’re used.
No if (env.IsDevelopment()) scattered through services.
No feature toggles are buried in components.
How This Works with AppSettings, User Secrets, and .env
Files
One of the biggest misconceptions around configuration is
thinking you have to choose between:
- appsettings.json
- User
Secrets
- Environment
variables
- .env
files
- Azure
App Settings
You don’t.
👉 They all work
together.
The key is understanding precedence and binding, not replacing
one with another.
The Configuration Stack (Highest Wins)
In Blazor (and .NET in general), configuration is layered.
Later sources override earlier ones.
Typical order:
- appsettings.json
- appsettings.{Environment}.json
- User
Secrets (Development only)
- Environment
variables
- Azure
App Service/container settings
Your AppConfig binding doesn’t change — only the source
of the values does.
AppSettings Files (Defaults and Baselines)
Use these for:
- Non-secret
defaults
- Shared
baseline values
- Documentation
of expected settings
// appsettings.json
{
"InCounter":
"",
"FeatureFlagStyleTable":
false
}
Think of this file as:
“Here’s what the app expects to exist.”
Not:
“Here’s what production should use.”
User Secrets (Local Development, No Secrets in Git)
User Secrets are perfect for:
- API
keys
- Local
URLs
- Developer-specific
overrides
dotnet user-secrets set "ApiBaseUrl" "https://localhost:7071"
They automatically override appsettings.json without
changing your code.
Your strongly-typed config still works exactly the same:
var apiUrl = appConfig.ApiBaseUrl;
No branching.
No conditionals.
No environment checks.
.env Files (Local Containers & Tooling)
.env files are not a native .NET feature — they’re a convention
used by:
- Docker
- Docker
Compose
- Some
local dev tools
Example:
ApiBaseUrl=https://localhost:7071
EnableNewFeature=true
When loaded, these become environment variables,
which .NET already understands.
If your tool loads .env into environment variables:
✔ They override appsettings
✔ They bind into AppConfig
✔ No code changes required
The configuration pipeline doesn’t care where the
value came from.
Azure App Settings (Production Overrides)
Azure App Service settings map directly to environment
variables.
That means:
- No
appsettings edits for prod
- No
secrets in source control
- No
redeploys to change values
Your Blazor app still just consumes:
IOptions<AppConfig>
This is where the pattern really shines:
The app never changes — only the configuration source does.
The Golden Rule
Your application code should never know:
- Where
configuration comes from
- Which
environment it is running in
- Whether
a value came from JSON, secrets, .env, or Azure
It should only know:
“I depend on this configuration contract.”
Why Strongly-Typed Config Makes This Safe
Without a config contract:
- Missing
values fail silently
- Typos
become runtime bugs
- Environment
overrides are invisible
With a contract + validation:
- Startup
fails fast
- Configuration
drift is obvious
- Overrides
are intentional
That’s the difference between configuration and configuration
chaos.
Bottom Line
Using AppConfig doesn’t replace:
- appsettings.json
- User
Secrets
- .env
files
- Azure
App Settings
It unifies them.
Same contract.
Same injection.
Same behavior.
Only the environment decides the values.
And that’s exactly how configuration should work.
Feature Flags Done Right
Instead of this:
if (Configuration["EnableNewFeature"] == "true")
{
// ...
}
Do this:
@if (AppConfig.EnableNewFeature)
{
<NewFeature
/>
}
Clear.
Predictable.
Testable.
Bonus: Making Configuration Visible at Runtime
One of the most underrated improvements:
Create a read-only configuration diagnostics page.
public record ConfigSnapshot(
string ApiBaseUrl,
bool
EnableNewFeature
);
Expose it:
- Only
in non-prod
- Or
behind admin authorization
This saves hours of “why is this behaving
differently?” debugging.
When This Pattern Really Pays Off
This approach shines when:
- You
have multiple Blazor apps
- You
deploy to Azure App Services
- You
use Key Vault
- You
support feature flags
- You
care about testability
- You
want predictable deployments
In other words: real systems.
Final Thought
Configuration should not be:
- A pile
of strings
- A
runtime guessing game
- A
tribal knowledge problem
It should be:
✅ Explicit
✅
Validated
✅
Environment-aware
✅
Boring
And a boring configuration is exactly what you want.

Comments
Post a Comment