Skip to content

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*.json file 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:BookingNumberPrefix and other domain values that differ per backend.
  • (Optionally) third-party API overrides under WhiteLabelingOptions:ExternalApis when 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 how prod.staging can point at prod-read-only while prod uses 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 any prod.* 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. Prefer dev or prod.staging (which uses prod-read-only) for investigation.
  • If you start more than one local service with AZURE_ENVIRONMENT set, 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_CONFIG and AZURE_ENVIRONMENT can be set at the same time, but then {service} tokens in appsettings.aspire.json are re-resolved against the Services entries from the azure layer (the rewriter runs once, last). If you want local Aspire wiring, leave AZURE_ENVIRONMENT unset.

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:

  1. Framework defaults (always loaded by Host.CreateApplicationBuilder or WebApplication.CreateBuilder):

    1. appsettings.json
    2. appsettings.{ASPNETCORE_ENVIRONMENT}.json — typically Development / Production
    3. User Secrets (only in Development)
    4. Environment variables
    5. Command-line args
  2. Aspire layer — only when USE_ASPIRE_CONFIG=true:

    • appsettings.aspire.json
  3. Azure layers — only when AZURE_ENVIRONMENT is set:

    1. appsettings.azure.json
    2. For each dot-separated segment of AZURE_ENVIRONMENT, append progressively:
      • e.g. AZURE_ENVIRONMENT=dev.hub-prod loads appsettings.azure.dev.json then appsettings.azure.dev.hub-prod.json.
    3. (DbMigrator only) the same progression with prefix azure.migrator, e.g. appsettings.azure.migrator.dev.json. This is the additionalSettingsPrefix argument of AddAzureJsonFiles; the migrator host passes "azure.migrator", every other host passes nothing.
    4. ConfigurationParameterReplacementSource — re-reads everything loaded so far and rewrites any value containing a {key} token by looking it up in the ConfigurationParameters section.
  4. White-label layer:

    • Path is WHITE_LABEL_SETTINGS_PATH if set, otherwise appsettings.rohlig.json (relative to the host's content root).
  5. Spark layer:

    • SPARK_ENVIRONMENT env var (default dev; valid: local, dev, test, prod, prod-read-only) is parsed into a SparkEnvironment and registered as a singleton service.
    • appsettings.spark.<env>.json (reloadOnChange: true)
    • appsettings.spark.<env>.user.json (reloadOnChange: true) — developer-local overrides; only the local variant is in .gitignore.
  6. Service-discovery rewriterServiceDiscoveryConfigurationSource (added by AddServiceDiscoveryConfiguration, after AddExtraConfigFiles) rewrites any value containing {service-name} using:

    1. services:<name>:https:0 — Aspire service endpoint (HTTPS)
    2. services:<name>:http:0 — Aspire service endpoint (HTTP)
    3. 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:0services:<n>:http:0ConnectionStrings:<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}:

  1. services:<name>:https:0 — Aspire injects this via services__<name>__https__0 env vars from WithServiceReference() in the AppHost.
  2. services:<name>:http:0
  3. ConnectionStrings:<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>() — wraps AddProject and sets USE_ASPIRE_CONFIG=true (EnvVars.UseAspireConfig) so the project picks up appsettings.aspire.json.
  • PassConfigurationValue(EnvVars.SparkEnvironmentName) — the generic PassConfigurationValue(string path) helper reads builder.ApplicationBuilder.Configuration[path] from the AppHost's own configuration and re-emits it on the child project's environment under the same key. Called with EnvVars.SparkEnvironmentName (which is the literal "SPARK_ENVIRONMENT") on the API host in Program.cs, it propagates the AppHost's SPARK_ENVIRONMENT down to that one child. (Note: the helper is generic and copies any config value by path; the local variable inside it is just named sparkEnvironment for the original call site — there is no WithSparkEnvironmentName method.)
  • WithWhiteLabelSettingsPath() — in run mode only (IfRunMode), sets WHITE_LABEL_SETTINGS_PATH to Path.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) — emits services__<name>__https__0 so the child can resolve {name} tokens. In publish mode it uses https://<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 path suffix.

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.

  1. Pick a non-production Azure env (e.g. dev, or prod.staging which is backed by prod-read-only). Do not use prod or a bare prod.* name unless you have a specific, verified reason.
  2. 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=Development if you want dev-friendly logging
  3. Do not start the AppHost. Skip USE_ASPIRE_CONFIG. The azure layer provides both the backend connections and the Services block, so {api}, {auth}, {realtime}, {web}, {admin} resolve to the public URLs.
  4. 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.

  1. Set APPHOST_AZURE_ENVIRONMENT (e.g. dev) on the AppHost process before dotnet run --project src/Cargonerds.AppHost. In run mode, SparkDbRunModeConfiguration.Create loads appsettings.azure.json + the dotted children from Cargonerds.ServiceDefaults, resolves the {spark-db-name}-style ConfigurationParameters, and uses the resulting ConnectionStrings:Default for the local Default connection string instead of spinning up the SQL container.
  2. To avoid touching the real Azure DB, set APPHOST_AZURE_DATABASE_COPY_SUFFIX to a suffix (e.g. -mycopy). The AppHost appends it to ConfigurationParameters: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.
  3. Guardrail: if APPHOST_AZURE_ENVIRONMENT starts with prod. you must supply APPHOST_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

  1. Pick a dotted name, e.g. dev.hub-test.
  2. Create src/Cargonerds.ServiceDefaults/appsettings.azure.dev.hub-test.json with only the values that differ from the parent layer (appsettings.azure.dev.json). Typically that's a few ConfigurationParameters overrides and any per-tenant Services entries.
  3. Set AZURE_ENVIRONMENT=dev.hub-test on the deployment target.
  4. (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

  1. Create src/<Project>/appsettings.spark.local.user.json (gitignored).
  2. Put the override under the right section.
  3. Run dotnet run --project src/Cargonerds.AppHost with SPARK_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:

{ "Some": { "Url": "{web}/some-path" } }

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

  1. In the most general layer that should define a default (often appsettings.azure.json or appsettings.azure.<top>.json), add the value under ConfigurationParameters.
  2. Reference it as {my-parameter} anywhere in any value of any azure file.
  3. Override it in nested layers (appsettings.azure.<top>.<child>.json) by re-declaring the same key under ConfigurationParameters.

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 to T; throws InvalidOperationException if it can't bind (the message names the key and target type).
  • configuration.GetAppConfiguration() — binds the App: section to AppConfiguration (SelfUrl, CorsOrigins, CargonerdsCustomerId, BookingNumberPrefix, …) and throws when the section is absent.
  • configuration.GetRequiredConnectionString("Default") — wraps GetConnectionString and throws "Connection string 'Default' not found in configuration." when missing. Pair it with the ConnectionStringNames constants instead of literals.
  • configuration.GetAzureEnvironment() — returns the current AZURE_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 .NET configuration layering used here is the standard IConfigurationBuilder provider model; the local-orchestration layer is .NET Aspire, which injects the services__* endpoint variables the service-discovery rewriter consumes.