Azure KeyVault for DBContext: "No database provider has been configured for this DbContext"

azure azure-keyvault c# entity-framework-core

Question

I am building a Web API using .NET Core 2.1. This will be hosted as an Azure Web App. I want to keep my database connection string in my Azure Key Vault. This is the code that I have put in my Startup.cs ConfigureServices method:

services.AddDbContext<dbContext>(async options => 
        {
            var keyVaultUri = new Uri("https://xxxxxxxxx.vault.azure.net/");
            var azureServiceTokenProvider = new AzureServiceTokenProvider();
            var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
            SecretBundle connectionStringSecret = await keyVaultClient.GetSecretAsync(keyVaultUri + "secrets/DBConnectionString");
            options.UseSqlServer(connectionStringSecret.Value);
        });

When I try to make an HTTP Get to my controller that has the dbContext injected into it I get the following error:

InvalidOperationException: No database provider has been configured 
for this DbContext. A provider can be configured by overriding the
DbContext.OnConfiguring method or by using AddDbContext on the 
application service provider. If AddDbContext is used, then also 
ensure that your DbContext type accepts a DbContextOptions<TContext> 
object in its constructor and passes it to the base constructor for 
DbContext.

I am assuming that is is because of my use of the async lambda to get the connection string from the Key Vault, however, I'm not sure what to do about it. Is this the right way to get a connection string from Azure KeyVault for use in the Startup.cs? Should I do this a different way? Any help would be appreciated. Thanks.

1
0
11/1/2018 2:23:39 PM

Accepted Answer

The reason your configuration fails is because the callback is now async void. This is not obvious from looking at the lambda, but it is effectively fire and forget. When you await the key vault client, it returns from the callback without configuring the provider.

As for a solution, I think it is better to add the key vault secrets into the config system so you can use them from there as if they came in from a JSON file or any other source.

I wrote an article on this a while back: https://joonasw.net/view/aspnet-core-azure-keyvault-msi. I used managed identity to authenticate in my article, but I see you are using it too :)

Here is a sample for how you can configure Key Vault as a configuration source in ASP.NET Core 2.x:

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureAppConfiguration((ctx, builder) =>
        {
            //Build the config from sources we have
            var config = builder.Build();
            //Add Key Vault to configuration pipeline
            builder.AddAzureKeyVault(config["KeyVault:BaseUrl"]);
        })
        .Build();

The sample I have in the article is actually a bit over-verbose as this will use the AzureServiceTokenProvider internally to acquire tokens.

You'll need Microsoft.Extensions.Configuration.AzureKeyVault to get the configuration provider for Key Vault.

The secret naming in Key Vault will matter. For example, we will override the following connection string:

{
  "ConnectionStrings": {
    "DefaultConnection": "..."
  }
}

You would have to create a secret named ConnectionStrings--DefaultConnection with the connection string as the value.

Then while configuring you just use Configuration["ConnectionStrings:DefaultConnection"] to get the connection string. It'll come from Key Vault if Key Vault config was added and a secret with the right name was found.

4
11/1/2018 5:27:08 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow