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:VaultUrlorVaultUri - an identity that
DefaultAzureCredentialcan 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:
Program.csbuilds configuration from JSON files and environment variables.- It locates the Key Vault URL from configuration such as
AzureKeyVault:VaultUrlorVaultUri. - It authenticates to Azure Key Vault using
DefaultAzureCredential. - It retrieves required secrets, including
ConnectionStrings--DefaultConnection. - It adds those values back into the in-memory configuration collection.
- It registers the EF Core
DbContextusing 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:
- Confirm the app can find the Key Vault URL.
- Confirm
DefaultAzureCredentialcan authenticate in the current environment. - Confirm the secret name is exactly
ConnectionStrings--DefaultConnection. - Confirm the secret is re-added as
ConnectionStrings:DefaultConnection. - Confirm the
DbContextis 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
DefaultAzureCredentialcan 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--DefaultConnectionsecret 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.csfor configuration source order- the Key Vault URL lookup logic
- the
DefaultAzureCredentialsetup - 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.
Leave a Reply