Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,20 @@ namespace Microsoft.Extensions.Configuration.EnvironmentVariables
/// </summary>
public class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
{
// Connection string prefixes for various services. These prefixes are used to identify connection strings in environment variables.
// az webapp config connection-string set: https://learn.microsoft.com/en-us/cli/azure/webapp/config/connection-string?view=azure-cli-latest#az-webapp-config-connection-string-set
// Environment variables and app settings in Azure App Service: https://learn.microsoft.com/en-us/azure/app-service/reference-app-settings?tabs=kudu%2Cdotnet#variable-prefixes
private const string MySqlServerPrefix = "MYSQLCONNSTR_";
private const string SqlAzureServerPrefix = "SQLAZURECONNSTR_";
private const string SqlServerPrefix = "SQLCONNSTR_";
private const string CustomConnectionStringPrefix = "CUSTOMCONNSTR_";
private const string PostgreSqlServerPrefix = "POSTGRESQLCONNSTR_";
private const string ApiHubPrefix = "APIHUBCONNSTR_";
private const string DocDbPrefix = "DOCDBCONNSTR_";
private const string EventHubPrefix = "EVENTHUBCONNSTR_";
private const string NotificationHubPrefix = "NOTIFICATIONHUBCONNSTR_";
private const string RedisCachePrefix = "REDISCACHECONNSTR_";
private const string ServiceBusPrefix = "SERVICEBUSCONNSTR_";

private readonly string _prefix;
private readonly string _normalizedPrefix;
Expand Down Expand Up @@ -83,6 +93,34 @@ internal void Load(IDictionary envVariables)
{
HandleMatchedConnectionStringPrefix(data, SqlServerPrefix, "System.Data.SqlClient", key, value);
}
else if (key.StartsWith(PostgreSqlServerPrefix, StringComparison.OrdinalIgnoreCase))
{
HandleMatchedConnectionStringPrefix(data, PostgreSqlServerPrefix, "Npgsql", key, value);
}
else if (key.StartsWith(ApiHubPrefix, StringComparison.OrdinalIgnoreCase))
{
HandleMatchedConnectionStringPrefix(data, ApiHubPrefix, null, key, value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume for all these that it's expected that we don't use a provider name?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. Non-relational services—such as API Hub, Cosmos DB, Event Hubs, Notification Hubs, Redis Cache, Service Bus, and others—do not use traditional ADO.NET providers. The documentation at this link outlines which prefix is injected into a .NET app along with its corresponding provider. Additionally, in the related issue thread, it was noted that a workaround is to define a custom connection string that already uses a null provider.

Copy link
Member

@ericstj ericstj May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we're adding a 1st class support, does it help to set a provider name though? How does that get used by folks? If it is intentional that we don't set a provider name then it would be good to capture that reasoning in a comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a tracking issue #116226 to follow up for provider names so we don't have to block this PR.

}
else if (key.StartsWith(DocDbPrefix, StringComparison.OrdinalIgnoreCase))
{
HandleMatchedConnectionStringPrefix(data, DocDbPrefix, null, key, value);
}
else if (key.StartsWith(EventHubPrefix, StringComparison.OrdinalIgnoreCase))
{
HandleMatchedConnectionStringPrefix(data, EventHubPrefix, null, key, value);
}
else if (key.StartsWith(NotificationHubPrefix, StringComparison.OrdinalIgnoreCase))
{
HandleMatchedConnectionStringPrefix(data, NotificationHubPrefix, null, key, value);
}
else if (key.StartsWith(RedisCachePrefix, StringComparison.OrdinalIgnoreCase))
{
HandleMatchedConnectionStringPrefix(data, RedisCachePrefix, null, key, value);
}
else if (key.StartsWith(ServiceBusPrefix, StringComparison.OrdinalIgnoreCase))
{
HandleMatchedConnectionStringPrefix(data, ServiceBusPrefix, null, key, value);
}
else if (key.StartsWith(CustomConnectionStringPrefix, StringComparison.OrdinalIgnoreCase))
{
HandleMatchedConnectionStringPrefix(data, CustomConnectionStringPrefix, null, key, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,5 +325,61 @@ private sealed class MyOptions
public int Number { get; set; }
public string Text { get; set; }
}

[Fact]
public void LoadsPostgreSqlConnectionStrings()
{
var dic = new Hashtable()
{
{"POSTGRESQLCONNSTR_db1", "Host=server1;Database=db1;Username=root;Password=password;"},
};
var envConfigSrc = new EnvironmentVariablesConfigurationProvider(null);

envConfigSrc.Load(dic);

Assert.Equal("Host=server1;Database=db1;Username=root;Password=password;", envConfigSrc.Get("ConnectionStrings:db1"));
Assert.Equal("Npgsql", envConfigSrc.Get("ConnectionStrings:db1_ProviderName"));
}

[Fact]
public void LoadsAzureServiceConnectionStrings()
{
var dic = new Hashtable()
{
{"APIHUBCONNSTR_api1", "Endpoint=https://api1.azure.com;ApiKey=key1;"},
{"DOCDBCONNSTR_docdb1", "AccountEndpoint=https://docdb1.documents.azure.com;AccountKey=key1;"},
{"EVENTHUBCONNSTR_eventhub1", "Endpoint=sb://eventhub1.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=key1;"},
{"NOTIFICATIONHUBCONNSTR_notification1", "Endpoint=sb://notification1.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=key1;"},
{"REDISCACHECONNSTR_redis1", "redis1.redis.cache.windows.net:6380,password=key1,ssl=True,abortConnect=False"},
{"SERVICEBUSCONNSTR_servicebus1", "Endpoint=sb://servicebus1.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=key1;"},
};
var envConfigSrc = new EnvironmentVariablesConfigurationProvider(null);

envConfigSrc.Load(dic);

// API Hub
Assert.Equal("Endpoint=https://api1.azure.com;ApiKey=key1;", envConfigSrc.Get("ConnectionStrings:api1"));
Assert.Throws<InvalidOperationException>(() => envConfigSrc.Get("ConnectionStrings:api1_ProviderName"));

// DocDB (Cosmos DB)
Assert.Equal("AccountEndpoint=https://docdb1.documents.azure.com;AccountKey=key1;", envConfigSrc.Get("ConnectionStrings:docdb1"));
Assert.Throws<InvalidOperationException>(() => envConfigSrc.Get("ConnectionStrings:docdb1_ProviderName"));

// Event Hub
Assert.Equal("Endpoint=sb://eventhub1.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=key1;", envConfigSrc.Get("ConnectionStrings:eventhub1"));
Assert.Throws<InvalidOperationException>(() => envConfigSrc.Get("ConnectionStrings:eventhub1_ProviderName"));

// Notification Hub
Assert.Equal("Endpoint=sb://notification1.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=key1;", envConfigSrc.Get("ConnectionStrings:notification1"));
Assert.Throws<InvalidOperationException>(() => envConfigSrc.Get("ConnectionStrings:notification1_ProviderName"));

// Redis Cache
Assert.Equal("redis1.redis.cache.windows.net:6380,password=key1,ssl=True,abortConnect=False", envConfigSrc.Get("ConnectionStrings:redis1"));
Assert.Throws<InvalidOperationException>(() => envConfigSrc.Get("ConnectionStrings:redis1_ProviderName"));

// Service Bus
Assert.Equal("Endpoint=sb://servicebus1.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=key1;", envConfigSrc.Get("ConnectionStrings:servicebus1"));
Assert.Throws<InvalidOperationException>(() => envConfigSrc.Get("ConnectionStrings:servicebus1_ProviderName"));
}
}
}
Loading