Configuration Settings¶
How .NET configuration is composed in CargonerdsApp, what each layer is for, and how to change values for a given scenario.
The mechanism lives in src/Cargonerds.ServiceDefaults/. Every host project (Cargonerds.HttpApi.Host, Cargonerds.AuthServer, Cargonerds.Web.Public, Cargonerds.Blazor, Cargonerds.DbMigrator) calls builder.AddServiceDefaults() during startup, which wires up the layered loading and the two token-replacement systems described below.
Related pages
- appsettings reference — the exhaustive, per-host, key-by-key catalogue of every
appsettings*.jsonfile and committed value. Read this page for the loading model; read that one to look up an individual key. - Deployment Configuration — the deployment-time mechanics (how values reach Azure Container Apps / App Service, CI secrets, the no-Key-Vault posture). Same two environment axes, viewed from the deployment side.
TL;DR — where to put a value¶
| You want to change… | Edit this file |
|---|---|
| Local default for a single service | src/<Project>/appsettings.json |
| Local dev-only override for a single service | src/<Project>/appsettings.Development.json |
| Aspire-orchestrated values (URLs that depend on running services) | src/<Project>/appsettings.aspire.json |
| White-label appearance, OIDC tenant, third-party API keys | src/Cargonerds.AppHost/appsettings.rohlig.json |
Spark-environment-specific value (local/dev/test/prod/prod-read-only) |
src/<Project>/appsettings.spark.<env>.json |
Personal overrides on your machine (gitignored for local) |
src/<Project>/appsettings.spark.<env>.user.json |
Azure-deployed environment (e.g. dev, dev.hub-prod, prod, prod.staging) |
src/Cargonerds.ServiceDefaults/appsettings.azure.<env>.json |
| Migrator-only Azure overrides | src/Cargonerds.DbMigrator/appsettings.azure.migrator.<env>.json |
Reusable parameter substituted into other values via {token} |
ConfigurationParameters section of the relevant azure file |
After choosing a file, restart the affected host (or rebuild the AppHost graph) — only Spark JSON files are loaded with reloadOnChange: true.
What the environment axes mean¶
The loading pipeline combines several layers, but two axes carry most of the per-deployment meaning: Spark and Azure. They are orthogonal — each answers a different question — and understanding the split is the fastest way to decide where a value belongs.
Spark environment — which external backend am I talking to?¶
Controlled by SPARK_ENVIRONMENT (one of local, dev, test, prod, prod-read-only; default dev). The matching appsettings.spark.<env>.json file pins the connections and identifiers that define the external data stack:
ConnectionStrings:Hub— the Cargonerds hub SQL database (each Spark env has its own server and database:sql-cargonerds-db-gerwc-development,…-test,…-production, etc.).ConnectionStrings:BlobStorage— the Azure Storage account (e.g.cnhubgerwcdevelopment,cnhubgerwctest,cnhubgerwcproduction) used for file uploads and derived artifacts.ConnectionStrings:hubServiceBus— the Azure Service Bus namespace (e.g.cn-hub-gerwc-development) used for inter-service messaging.App:CargonerdsCustomerId— the tenant identifier that scopes data access.App:BookingNumberPrefixand other domain values that differ per backend.- (Optionally) third-party API overrides under
WhiteLabelingOptions:ExternalApiswhen a tenant-specific key is needed.
Concretely:
| Spark env | Hub DB | Blob storage | Intended use |
|---|---|---|---|
local |
localhost SQL on :1401 |
Azurite (devstoreaccount1) |
Fully offline development with the Aspire stack. |
dev |
sql-cargonerds-db-gerwc-development |
cnhubgerwcdevelopment |
Shared development backend. |
test |
sql-cargonerds-db-gerwc-test |
cnhubgerwctest |
QA / UAT. |
prod |
sql-cargonerds-db-gerwc-production (read-write user) |
cnhubgerwcproduction |
Live production. |
prod-read-only |
same production DB with a read-only SQL user | cnhubgerwcproduction |
Production backend without write risk — used by the prod.staging Azure slot and for prod investigations. |
These are exactly the five names defined on the SparkEnvironment type (modules/hub/src/Hub.Domain.Shared/Consts/SparkEnvironment.cs), registered in a case-insensitive lookup. SparkEnvironment.FromName returns Dev for a null/blank value but throws ArgumentException("Unknown SparkEnvironment name: …") for any other unrecognised string — so a typo in SPARK_ENVIRONMENT (e.g. prod-readonly instead of prod-read-only) fails the host at startup rather than silently falling back.
The same Spark env is often used by multiple running instances. That's the whole reason the Spark layer is separate from the Azure layer: the backend (DB, storage, bus) is a property of a data stack; the front-of-cluster config (which app URL, which tenant domain, which white-label) can vary per deployment while pointing at the same backend. For example, both appsettings.azure.dev.json and appsettings.azure.dev.hub-prod.json set SPARK_ENVIRONMENT but to different values (dev vs. prod-read-only) — one is a real dev instance, the other is a staging copy of prod running on dev infrastructure.
Only Cargonerds.HttpApi.Host currently ships per-Spark-env files, because it's the service that owns the Hub-side connections. Under Aspire it is also the only project the AppHost explicitly forwards SPARK_ENVIRONMENT to — via apiHost.PassConfigurationValue(EnvVars.SparkEnvironmentName) (see How AppHost composes the variables). Other hosts read whatever SPARK_ENVIRONMENT is present in their own process environment (inherited from the launching shell, or unset → dev) and register the resulting SparkEnvironment as a singleton.
Azure environment — which App Service shape am I impersonating?¶
Controlled by AZURE_ENVIRONMENT. Unset by default. When set, the progressive azure layers kick in (see the loading pipeline section below). The files in src/Cargonerds.ServiceDefaults/appsettings.azure*.json hold the deployment-shape values:
ConnectionStrings:Default— the ABP application database (separate from the Hub DB) for this deployment.ConnectionStrings:redis/ConnectionStrings:messaging— the Redis cache and RabbitMQ instance for this deployment.Services:<name>:https— the public URLs that{service-name}tokens resolve to at runtime. In the azure layer these are real hostnames (e.g.https://api.rt3.rohlig.com,https://api.dev.spark.cargonerds.dev) instead of the localhost Aspire endpoints.ConfigurationParameters— the static values (spark-dev-domain,spark-db-name,rabbit-mq-port) that get stamped into the connection strings and URLs above via{token}substitution.SPARK_ENVIRONMENT— each azure file pins its expected backend, which is howprod.stagingcan point atprod-read-onlywhileproduses live prod.
Primary use: deployed Azure App Services. Each App Service sets AZURE_ENVIRONMENT to the dotted name of its slot (dev, dev.hub-prod, prod, prod.staging, …), and the host composes the right layer stack automatically.
Secondary use: debug locally with production-shape neighbors. If you set AZURE_ENVIRONMENT on your machine you do not need to run the full Aspire stack. Start only the one service you want to debug; the service-discovery rewriter will resolve {api}, {auth}, {realtime}, {web}, {admin} against the Services block from the azure files — i.e. against the live deployed URLs. This is the recommended way to step through a single service while it interacts with a realistic set of neighbors.
Caveats
- Never set
AZURE_ENVIRONMENT=prod(or anyprod.*value) unless you are absolutely sure. Your local process will connect to production databases, storage, and service bus, and any write you trigger hits real customer data. Preferdevorprod.staging(which usesprod-read-only) for investigation. - If you start more than one local service with
AZURE_ENVIRONMENTset, they do not talk to each other — they each independently talk to the corresponding deployed App Service, because{service}tokens resolve to the public URLs, not to your localhost ports. This is usually fine (and often the point), but don't expect cross-service traffic between two locally running hosts in this mode. USE_ASPIRE_CONFIGandAZURE_ENVIRONMENTcan be set at the same time, but then{service}tokens inappsettings.aspire.jsonare re-resolved against theServicesentries from the azure layer (the rewriter runs once, last). If you want local Aspire wiring, leaveAZURE_ENVIRONMENTunset.
AZURE_ENVIRONMENT vs. the AppHost's own APPHOST_AZURE_ENVIRONMENT
The per-service AZURE_ENVIRONMENT above is read by every host through configuration.GetAzureEnvironment(). The AppHost process itself has a separate selector, APPHOST_AZURE_ENVIRONMENT (constant EnvVars.AzureEnvironmentName), used only in Aspire run mode to attach the local stack to an Azure spark database. See Run the Aspire stack against an Azure spark database — the two variables are deliberately distinct so that running the orchestrator against a remote DB never accidentally flips the child services into deployed-shape mode.
The loading pipeline¶
AddServiceDefaults() calls (in order) AddExtraConfigFiles() and AddServiceDiscoveryConfiguration(). The full effective ordering, including the framework defaults that already loaded before AddServiceDefaults runs, is:
-
Framework defaults (always loaded by
Host.CreateApplicationBuilderorWebApplication.CreateBuilder):appsettings.jsonappsettings.{ASPNETCORE_ENVIRONMENT}.json— typicallyDevelopment/Production- User Secrets (only in
Development) - Environment variables
- Command-line args
-
Aspire layer — only when
USE_ASPIRE_CONFIG=true:appsettings.aspire.json
-
Azure layers — only when
AZURE_ENVIRONMENTis set:appsettings.azure.json- For each dot-separated segment of
AZURE_ENVIRONMENT, append progressively:- e.g.
AZURE_ENVIRONMENT=dev.hub-prodloadsappsettings.azure.dev.jsonthenappsettings.azure.dev.hub-prod.json.
- e.g.
- (DbMigrator only) the same progression with prefix
azure.migrator, e.g.appsettings.azure.migrator.dev.json. This is theadditionalSettingsPrefixargument ofAddAzureJsonFiles; the migrator host passes"azure.migrator", every other host passes nothing. ConfigurationParameterReplacementSource— re-reads everything loaded so far and rewrites any value containing a{key}token by looking it up in theConfigurationParameterssection.
-
White-label layer:
- Path is
WHITE_LABEL_SETTINGS_PATHif set, otherwiseappsettings.rohlig.json(relative to the host's content root).
- Path is
-
Spark layer:
SPARK_ENVIRONMENTenv var (defaultdev; valid:local,dev,test,prod,prod-read-only) is parsed into aSparkEnvironmentand registered as a singleton service.appsettings.spark.<env>.json(reloadOnChange: true)appsettings.spark.<env>.user.json(reloadOnChange: true) — developer-local overrides; only thelocalvariant is in.gitignore.
-
Service-discovery rewriter —
ServiceDiscoveryConfigurationSource(added byAddServiceDiscoveryConfiguration, afterAddExtraConfigFiles) rewrites any value containing{service-name}using:services:<name>:https:0— Aspire service endpoint (HTTPS)services:<name>:http:0— Aspire service endpoint (HTTP)ConnectionStrings:<name>— named connection string
Each later layer wins. Within the framework's standard chain, environment variables override JSON files. Within the custom layers, the order above is the precedence.
All layers are optional JSON except white-label-via-AppHost
Every AddJsonFile call in AddExtraConfigFiles passes optional: true, so a missing appsettings.aspire.json, appsettings.spark.<env>.json, etc. is silently skipped — a host started without the Aspire/Azure/Spark files simply runs on its framework defaults plus white-label. The two source files that are not optional both live in AppHost code paths: Cargonerds.AppHost/Program.cs requires appsettings.rohlig.json, and the AppHost's AddAzureEnvironmentConfiguration (run-mode Azure attach, below) loads the azure files with optional: false.
Why two parameter rewriters exist
AddAzureJsonFiles builds an interim IConfiguration from the azure files (builder.Build()) and hands it to ConfigurationParameterReplacementSource. That snapshot is taken before the white-label and Spark layers load, so {key} parameter substitution only sees values present in the azure files (plus everything loaded earlier). Service-discovery tokens, by contrast, are resolved last against the fully merged configuration — which is why appsettings.aspire.json {service} tokens can see endpoints injected at the very end.
See: src/Cargonerds.ServiceDefaults/HostApplicationBuilderExtensions.cs:55 for AddExtraConfigFiles, :159 for AddServiceDiscoveryConfiguration, and :172 (the ConfigurationBuilderExtensions.AddAzureJsonFiles extension member, which adds the ConfigurationParameterReplacementSource at :193).
Driving environment variables¶
| Variable | Read by | Effect | Default |
|---|---|---|---|
ASPNETCORE_ENVIRONMENT |
Framework | Picks appsettings.{Env}.json and toggles User Secrets. |
Production |
USE_ASPIRE_CONFIG |
AddExtraConfigFiles |
Loads appsettings.aspire.json when true. AppHost sets it via AddProjectWithDefaults (EnvVars.UseAspireConfig). |
false |
AZURE_ENVIRONMENT |
AddExtraConfigFiles, GetAzureEnvironment() |
Triggers the per-host Azure layered load; the dotted value drives which files are appended. | unset |
SPARK_ENVIRONMENT |
AddExtraConfigFiles, SparkEnvironment.FromName |
Picks appsettings.spark.<env>.json and registers SparkEnvironment. Unknown value → startup ArgumentException. |
dev |
WHITE_LABEL_SETTINGS_PATH |
AddExtraConfigFiles |
Absolute or relative path to white-label JSON. AppHost passes the absolute path during run mode (WithWhiteLabelSettingsPath). |
appsettings.rohlig.json |
DEPLOYMENT_DOMAIN |
AppHost (EnvVars.DeploymentDomain, ResourceBuilderExtensions) |
Hostname suffix used in publish mode for services__* env vars. Getter throws if unset in publish mode. |
required in publish mode |
APPHOST_AZURE_ENVIRONMENT |
AppHost (SparkDbRunModeConfiguration) |
In Aspire run mode, points the local Default connection string at an Azure spark DB. Does not affect child-service AZURE_ENVIRONMENT. |
unset |
APPHOST_AZURE_DATABASE_COPY_SUFFIX |
AppHost (SparkDbRunModeConfiguration) |
Suffix appended to ConfigurationParameters:spark-db-name so the AppHost targets a copy of the Azure DB. Required when APPHOST_AZURE_ENVIRONMENT starts with prod.. |
unset |
OTEL_EXPORTER_OTLP_ENDPOINT |
AddOpenTelemetryExporters |
Enables the OTLP exporter when non-empty. | unset |
APPLICATIONINSIGHTS_CONNECTION_STRING |
AddOpenTelemetryExporters |
Enables Azure Monitor when non-empty. | unset |
Names are centralised
The AppHost-side variable names live in one place — src/Cargonerds.AppHost/EnvVars.cs — with typed accessors (EnvVars.AzureEnvironment, EnvVars.DeploymentDomain, …). Prefer those constants over string literals when adding wiring so the spelling stays consistent across Program.cs and the extension methods.
Token replacement — two systems with the same syntax¶
Both run as IConfigurationSources and both look like {name} — but they have different lookup tables, different regexes, and run at different points.
ConfigurationParameters engine |
Service-discovery engine | |
|---|---|---|
| Provider | ConfigurationParameterReplacementProvider |
ServiceDiscoveryConfigurationProvider |
| Token regex | \{([^{}]+)\} — any run of non-brace chars (so spaces and dots are allowed inside) |
\{([\w-]+)\} — word chars + hyphen only |
| Lookup table | the ConfigurationParameters:* section |
services:<n>:https:0 → services:<n>:http:0 → ConnectionStrings:<n> |
| When | end of the Azure layer (over the azure-files snapshot) | last, over the fully merged config |
| Unmatched token | left literal ({name} stays in the value) |
throws InvalidOperationException on startup |
That regex difference is deliberate and occasionally load-bearing: a leftover token containing a space or other non-word character (e.g. a malformed {spark db name}) is not matched by the service-discovery regex, so it slips through unresolved instead of failing fast. Keep parameter and service names to [A-Za-z0-9_-].
1. ConfigurationParameters (static parameter inheritance)¶
Source: ConfigurationParameterReplacementProvider.cs. Runs at the end of the Azure layer load.
Use this to keep multi-layer azure files DRY. Define a value once, reference it everywhere:
// appsettings.azure.dev.json
{
"ConnectionStrings": {
"Default": "Server=tcp:sql-spark-dev.database.windows.net,1433;Initial Catalog={spark-db-name}",
"messaging": "amqp://user:pwd@rabbitmq.dev.spark.cargonerds.dev:{rabbit-mq-port}/"
},
"ConfigurationParameters": {
"spark-db-name": "spark-dev",
"rabbit-mq-port": 5672
}
}
A nested layer can override just the parameter to retarget every reference:
// appsettings.azure.dev.hub-prod.json
{
"ConfigurationParameters": {
"spark-db-name": "spark-dev-hub-prod",
"rabbit-mq-port": 5673
}
}
A ConfigurationParameters section is mandatory once any azure file loads
The provider calls configuration.GetRequiredSection("ConfigurationParameters"). If the merged azure layer has no ConfigurationParameters section at all (none of appsettings.azure.json or the dotted children defines one), GetRequiredSection throws and the host fails to start — even when no value actually uses a {token}. Always declare at least an empty "ConfigurationParameters": {} in the most general azure file.
If a token has no matching parameter, the literal {name} remains in the value. If that leftover token also matches the service-discovery pattern ([\w-]+) and resolves to no service or connection string, the service-discovery rewriter (next step) is what throws and shuts the app down — failing fast on a genuinely unresolved name.
2. Service-discovery tokens (runtime endpoint resolution)¶
Source: ServiceDiscoveryConfigurationProvider.cs. Runs last, after every layer is loaded.
Use this in any value that should resolve to a sibling service's URL or a named connection string. The canonical place is appsettings.aspire.json:
// appsettings.aspire.json (HttpApi.Host)
{
"App": {
"SelfUrl": "{api}",
"CorsOrigins": "{realtime},{admin}"
},
"AuthServer": {
"Authority": "{auth}"
},
"Redis": {
"Configuration": "{redis}"
}
}
Lookup order for {name}:
services:<name>:https:0— Aspire injects this viaservices__<name>__https__0env vars fromWithServiceReference()in the AppHost.services:<name>:http:0ConnectionStrings:<name>
If none match, the host throws on startup with the offending key and a sorted list of every available service / connection-string name (gathered from the services and ConnectionStrings sections) — failing fast prevents silent misconfiguration and tells you exactly what you could have meant.
Note
appsettings.azure.*.json files use the ConfigurationParameters mechanism (static), while appsettings.aspire.json uses service-discovery (runtime). The braces look identical but the resolution scope is different — keep that in mind when copying snippets between files.
Canonical token / connection-string names¶
The names on the right are not magic strings scattered through the code — the connection-string keys are defined once on ConnectionStringNames (src/Cargonerds.Domain.Shared/Configuration/ConnectionStringNames.cs):
| Token / key | Constant | Resolves to |
|---|---|---|
{api}, {auth}, {admin}, {realtime}, {web} |
— (Aspire service names) | sibling host URL (Aspire endpoint locally, public URL in azure layer) |
{redis}, {messaging} |
— (Aspire resource names) | Redis cache / RabbitMQ connection string |
Default |
ConnectionStringNames.SparkDb |
ABP application DB (per Azure environment) |
Hub |
ConnectionStringNames.HubDb |
Cargonerds Hub SQL DB (per Spark environment) |
BlobStorage |
ConnectionStringNames.BlobStorage |
Azure Storage / Azurite account |
hubServiceBus |
ConnectionStringNames.HubServiceBus |
Azure Service Bus namespace |
How AppHost composes the variables¶
Cargonerds.AppHost controls which env vars each project receives. Key extension methods in src/Cargonerds.AppHost/Extensions/ResourceBuilderExtensions.cs:
AddProjectWithDefaults<T>()— wrapsAddProjectand setsUSE_ASPIRE_CONFIG=true(EnvVars.UseAspireConfig) so the project picks upappsettings.aspire.json.PassConfigurationValue(EnvVars.SparkEnvironmentName)— the genericPassConfigurationValue(string path)helper readsbuilder.ApplicationBuilder.Configuration[path]from the AppHost's own configuration and re-emits it on the child project's environment under the same key. Called withEnvVars.SparkEnvironmentName(which is the literal"SPARK_ENVIRONMENT") on the API host inProgram.cs, it propagates the AppHost'sSPARK_ENVIRONMENTdown to that one child. (Note: the helper is generic and copies any config value by path; the local variable inside it is just namedsparkEnvironmentfor the original call site — there is noWithSparkEnvironmentNamemethod.)WithWhiteLabelSettingsPath()— in run mode only (IfRunMode), setsWHITE_LABEL_SETTINGS_PATHtoPath.GetFullPath("appsettings.rohlig.json")so the child finds it regardless of working directory (Aspire run-mode processes use their project directory, not the build output, as the working directory).WithServiceReference(target)— emitsservices__<name>__https__0so the child can resolve{name}tokens. In publish mode it useshttps://<name>.<DEPLOYMENT_DOMAIN>instead of the local Aspire endpoint. An overload accepts several targets and aggregates them.WithEnvironmentHttpsEndpoint(...)/WithSelfEnvironmentHttpsEndpoint(...)— same dual run/publish behavior for one-off env vars that must hold a sibling (or the resource's own) URL, with an optional pathsuffix.
Where Program.cs wires it up:
// src/Cargonerds.AppHost/Program.cs
apiHost
// …health checks, waits, references…
.WithWhiteLabelSettingsPath()
.WithReference(db, "Default")
.PassConfigurationValue(EnvVars.SparkEnvironmentName);
Forwarding is per-project, not global
Only apiHost calls PassConfigurationValue(EnvVars.SparkEnvironmentName). The migrator, auth server and admin frontend do not receive a forwarded SPARK_ENVIRONMENT from the AppHost — they fall back to whatever is in their own process environment (or dev). If you need another host pinned to a specific Spark env under Aspire, either export SPARK_ENVIRONMENT before launching the AppHost or add a PassConfigurationValue call for that resource.
Recipes¶
Debug one service locally against a deployed Azure environment¶
Use this to step through a single host while it interacts with the real dev (or staging) neighbors — without spinning up the Aspire graph.
- Pick a non-production Azure env (e.g.
dev, orprod.stagingwhich is backed byprod-read-only). Do not useprodor a bareprod.*name unless you have a specific, verified reason. - Launch just the project you want to debug (from your IDE or
dotnet run --project src/Cargonerds.HttpApi.Host) with:AZURE_ENVIRONMENT=dev(or the env you chose)ASPNETCORE_ENVIRONMENT=Developmentif you want dev-friendly logging
- Do not start the AppHost. Skip
USE_ASPIRE_CONFIG. The azure layer provides both the backend connections and theServicesblock, so{api},{auth},{realtime},{web},{admin}resolve to the public URLs. - If you also start a second local service with the same
AZURE_ENVIRONMENT, note that they won't see each other — each still points at the deployed sibling. That's expected.
Run the Aspire stack against an Azure spark database¶
Use this when you want the full local Aspire graph (all services orchestrated locally) but with the Default (Spark/application) database living in Azure instead of the local SQL container — for example to reproduce a data-shape issue against real dev data.
- Set
APPHOST_AZURE_ENVIRONMENT(e.g.dev) on the AppHost process beforedotnet run --project src/Cargonerds.AppHost. In run mode,SparkDbRunModeConfiguration.Createloadsappsettings.azure.json+ the dotted children fromCargonerds.ServiceDefaults, resolves the{spark-db-name}-styleConfigurationParameters, and uses the resultingConnectionStrings:Defaultfor the localDefaultconnection string instead of spinning up the SQL container. - To avoid touching the real Azure DB, set
APPHOST_AZURE_DATABASE_COPY_SUFFIXto a suffix (e.g.-mycopy). The AppHost appends it toConfigurationParameters:spark-db-name, so the connection string targets a copy you have provisioned. When the suffix is set, the migrator is also allowed to apply migrations (the copy is writable); without it, the migrator runs in validate-only mode. - Guardrail: if
APPHOST_AZURE_ENVIRONMENTstarts withprod.you must supplyAPPHOST_AZURE_DATABASE_COPY_SUFFIX, otherwise the AppHost throws on startup — you cannot accidentally point the local orchestrator at the live production DB.
Note
This selector is the AppHost's alone. It does not set AZURE_ENVIRONMENT on the child services — they still run in Aspire mode (USE_ASPIRE_CONFIG=true) with {service} tokens resolving to local endpoints. Only the Default DB is redirected.
Add a new Azure deployment environment¶
- Pick a dotted name, e.g.
dev.hub-test. - Create
src/Cargonerds.ServiceDefaults/appsettings.azure.dev.hub-test.jsonwith only the values that differ from the parent layer (appsettings.azure.dev.json). Typically that's a fewConfigurationParametersoverrides and any per-tenantServicesentries. - Set
AZURE_ENVIRONMENT=dev.hub-teston the deployment target. - (Optional) If the migrator needs different settings, add
src/Cargonerds.DbMigrator/appsettings.azure.migrator.dev.hub-test.json.
Override a value just on your machine¶
- Create
src/<Project>/appsettings.spark.local.user.json(gitignored). - Put the override under the right section.
- Run
dotnet run --project src/Cargonerds.AppHostwithSPARK_ENVIRONMENT=local.
Point a service at a different sibling URL when running under Aspire¶
Edit appsettings.aspire.json in the consuming project and add the token:
Then ensure the AppHost wires the reference: consumer.WithServiceReference(web). The service-discovery rewriter resolves {web} to the live Aspire endpoint in run mode and to https://web.<DEPLOYMENT_DOMAIN> in publish mode.
Add a parameter shared across azure layers¶
- In the most general layer that should define a default (often
appsettings.azure.jsonorappsettings.azure.<top>.json), add the value underConfigurationParameters. - Reference it as
{my-parameter}anywhere in any value of any azure file. - Override it in nested layers (
appsettings.azure.<top>.<child>.json) by re-declaring the same key underConfigurationParameters.
Switch white-label tenant locally¶
Set WHITE_LABEL_SETTINGS_PATH to a different file (e.g. appsettings.othertenant.json) before launching, or change the file referenced by WithWhiteLabelSettingsPath() in the AppHost.
Reading values in code¶
src/Cargonerds.Domain.Shared/Extensions/ConfigurationExtensions.cs exposes typed helpers as C# extension members on IConfiguration:
configuration.GetRequiredValue<T>("Section")— binds the section toT; throwsInvalidOperationExceptionif it can't bind (the message names the key and target type).configuration.GetAppConfiguration()— binds theApp:section toAppConfiguration(SelfUrl,CorsOrigins,CargonerdsCustomerId,BookingNumberPrefix, …) and throws when the section is absent.configuration.GetRequiredConnectionString("Default")— wrapsGetConnectionStringand throws"Connection string 'Default' not found in configuration."when missing. Pair it with theConnectionStringNamesconstants instead of literals.configuration.GetAzureEnvironment()— returns the currentAZURE_ENVIRONMENT(nullable string).
For the Spark environment, inject SparkEnvironment directly — it's registered as a singleton during AddExtraConfigFiles().
Source map¶
| Concern | File |
|---|---|
| Pipeline entry point | src/Cargonerds.ServiceDefaults/HostApplicationBuilderExtensions.cs (AddServiceDefaults :27, AddExtraConfigFiles :55) |
| Service-discovery source registration | same file, AddServiceDiscoveryConfiguration :159 |
| Azure layer expansion | same file, ConfigurationBuilderExtensions.AddAzureJsonFiles :172 (adds the parameter source at :193) |
{key} parameter rewriter |
src/Cargonerds.ServiceDefaults/ConfigurationParameterReplacementProvider.cs (+ ConfigurationParameterReplacementSource.cs) |
{service-name} discovery rewriter |
src/Cargonerds.ServiceDefaults/ServiceDiscoveryConfigurationProvider.cs (+ ServiceDiscoveryConfigurationSource.cs) |
| Spark environment type | modules/hub/src/Hub.Domain.Shared/Consts/SparkEnvironment.cs |
| Connection-string name constants | src/Cargonerds.Domain.Shared/Configuration/ConnectionStringNames.cs |
| AppHost env-var wiring | src/Cargonerds.AppHost/Extensions/ResourceBuilderExtensions.cs, src/Cargonerds.AppHost/Program.cs |
| AppHost env-var name constants | src/Cargonerds.AppHost/EnvVars.cs |
| AppHost run-mode Azure DB attach | src/Cargonerds.AppHost/SparkDbRunModeConfiguration.cs (+ ConfigruationExtensions.cs) |
| Typed config helpers | src/Cargonerds.Domain.Shared/Extensions/ConfigurationExtensions.cs |
The
.NETconfiguration layering used here is the standardIConfigurationBuilderprovider model; the local-orchestration layer is .NET Aspire, which injects theservices__*endpoint variables the service-discovery rewriter consumes.