TekOnline

Connection String FAQ

This page explains a common ASP.NET Core pattern where the app does not keep its main database connection string in appsettings.json. Instead, startup code retrieves the value from Azure Key Vault and inserts it into the normal .NET configuration tree before the rest of the app runs.

What does the startup code actually look like?

The relevant pattern is small. This is the important part of Program.cs, simplified to the pieces that fetch the connection string and place it into in-memory configuration:

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
    .AddEnvironmentVariables();

var keyVaultUrl = builder.Configuration["AzureKeyVault:VaultUrl"]
    ?? builder.Configuration["VaultUri"]
    ?? Environment.GetEnvironmentVariable("VaultUri");

if (string.IsNullOrWhiteSpace(keyVaultUrl))
{
    throw new InvalidOperationException(
        "Azure Key Vault is not configured. Set AzureKeyVault:VaultUrl or VaultUri.");
}

var keyVaultClient = new SecretClient(
    new Uri(keyVaultUrl),
    new DefaultAzureCredential());

var connectionString = await GetRequiredKeyVaultSecretAsync(
    keyVaultClient,
    "ConnectionStrings--DefaultConnection");

builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
    ["ConnectionStrings:DefaultConnection"] = connectionString,
    ["Diagnostics:KeyVault:DefaultConnectionLoaded"] = "true"
});

builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString);
});

The helper that actually reads the secret can be as simple as:

static async Task<string> GetRequiredKeyVaultSecretAsync(
    SecretClient keyVaultClient,
    string secretName)
{
    var response = await keyVaultClient.GetSecretAsync(secretName);
    var value = response.Value.Value;

    if (string.IsNullOrWhiteSpace(value))
    {
        throw new InvalidOperationException(
            $"Azure Key Vault secret '{secretName}' was found but is empty.");
    }

    return value;
}

If I am setting this up from scratch, what do I need?

At minimum:

  • an Azure Key Vault
  • a secret named ConnectionStrings--DefaultConnection
  • a configured Key Vault URL such as AzureKeyVault:VaultUrl or VaultUri
  • an identity that DefaultAzureCredential can use
  • Key Vault permission for that identity to read secrets
  • startup code that writes the retrieved value back as ConnectionStrings:DefaultConnection

If any of those pieces are missing, startup will usually fail before EF Core can connect to the database.

Why is ConnectionStrings:DefaultConnection missing from appsettings.json?

Because the app currently loads that value from Azure Key Vault during startup.

That means appsettings.json does not need to contain the live database secret. By the time services are registered, the connection string has already been retrieved and placed back into configuration.

What secret name does Azure Key Vault use?

Azure Key Vault secret names cannot contain : characters, so a .NET configuration key such as:

ConnectionStrings:DefaultConnection

is typically stored in Key Vault as:

ConnectionStrings--DefaultConnection

At startup, the app reads the Key Vault secret and maps it back into the normal .NET configuration key:

ConnectionStrings:DefaultConnection

How does the startup flow work?

The startup pattern is:

  1. Program.cs builds configuration from JSON files and environment variables.
  2. It locates the Key Vault URL from configuration such as AzureKeyVault:VaultUrl or VaultUri.
  3. It authenticates to Azure Key Vault using DefaultAzureCredential.
  4. It retrieves required secrets, including ConnectionStrings--DefaultConnection.
  5. It adds those values back into the in-memory configuration collection.
  6. It registers the EF Core DbContext using the resolved connection string.

After that, the rest of the app behaves as though the connection string had been present in configuration from the start.

What does “putting it in memory” mean here?

It means the app adds a new configuration source during startup:

builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
    ["ConnectionStrings:DefaultConnection"] = connectionString
});

That does not write anything back to appsettings.json. It only stores the value in the running application’s configuration pipeline.

From that point on, code that reads:

builder.Configuration.GetConnectionString("DefaultConnection")

or any service registration that uses the same configuration key will see the Key Vault value.

Why do controllers and services still work if they never read the connection string?

Because most application code does not need to read the connection string directly.

In a typical ASP.NET Core app, Program.cs configures the EF Core DbContext once during startup. Controllers, services, view components, and Razor pages then receive that ready-to-use DbContext through dependency injection.

So the usual pattern is:

startup -> resolve secret -> configure DbContext -> inject DbContext into app code

Application code usually only knows about the DbContext, not where the underlying connection string came from.

How should I read this flow when troubleshooting?

A useful way to trace it is:

  1. Confirm the app can find the Key Vault URL.
  2. Confirm DefaultAzureCredential can authenticate in the current environment.
  3. Confirm the secret name is exactly ConnectionStrings--DefaultConnection.
  4. Confirm the secret is re-added as ConnectionStrings:DefaultConnection.
  5. Confirm the DbContext is registered using the resolved value.

If step 4 never happens, the rest of the app will not see the expected connection string key even if the secret fetch succeeded.

Does a blank local value in launchSettings.json override the Key Vault value?

Not if startup explicitly fetches the Key Vault secret and then writes it back into configuration afterward.

In that flow, the later in-memory value wins because it is added after the base configuration sources are loaded.

What usually fails first during local startup?

If the app fails before any database work happens, the first problem is often authentication or network access to Azure Key Vault, not SQL itself.

Common causes include:

  • no local Azure sign-in that DefaultAzureCredential can use
  • missing Key Vault permissions
  • an incorrect or missing VaultUri
  • blocked network access to the vault

Is runtime Key Vault access the same thing as CI/CD pipeline access?

No.

These are separate concerns:

  • Runtime access means the deployed application identity can read secrets while the app is starting.
  • CI/CD access means the build or release pipeline can read secrets during deployment steps.

A pipeline only needs direct Key Vault access if the pipeline itself must read or inject secrets before the app starts.

What logs confirm the connection string came from Key Vault?

Startup logs should include messages showing that:

  • Key Vault secret retrieval began
  • the ConnectionStrings--DefaultConnection secret was retrieved successfully
  • the final connection string source was Azure Key Vault

Example log patterns:

[AKV] Starting Key Vault secret retrieval from ...
[AKV] Secret 'ConnectionStrings--DefaultConnection' retrieved in ... ms.
[AKV] DefaultConnection loaded from Key Vault. ...
Connection string source: Azure Key Vault secret ConnectionStrings--DefaultConnection

What should I inspect in code?

If you are troubleshooting this pattern, inspect:

  • Program.cs for configuration source order
  • the Key Vault URL lookup logic
  • the DefaultAzureCredential setup
  • the secret name being requested
  • the AddInMemoryCollection(...) call that writes the value back into configuration
  • the AddDbContext(...) registration for the main database context

Short Version

If an ASP.NET Core app fetches ConnectionStrings--DefaultConnection from Azure Key Vault during startup and re-adds it as ConnectionStrings:DefaultConnection, then the rest of the app can use the database normally even when appsettings.json does not contain the live connection string.


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *