I am trying to update the value of one of the config values in Configure(IWebJobsBuilder builder) method, it works fine when running locally but when deployed in azure function throws exception.
Exception:
Message: A configuration source is not registered. Please register one before setting a value.
stack trace :
System.InvalidOperationException:
at Microsoft.Extensions.Configuration.ConfigurationRoot.set_Item (Microsoft.Extensions.Configuration, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
at Microsoft.Extensions.Configuration.ChainedConfigurationProvider.Set (Microsoft.Extensions.Configuration, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
at Microsoft.Extensions.Configuration.ConfigurationRoot.set_Item (Microsoft.Extensions.Configuration, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
at Microsoft.Extensions.Configuration.ChainedConfigurationProvider.Set (Microsoft.Extensions.Configuration, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
at Microsoft.Extensions.Configuration.ConfigurationRoot.set_Item (Microsoft.Extensions.Configuration, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60)
at ConvAi.BfChannel.BotManagementService.WebJobsExtensionStartup.Configure (ConvAi.BfChannel.BotManagementService, Version=0.0.0.0, Culture=neutral, PublicKeyToken=nullConvAi.BfChannel.BotManagementService, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null: E:\Git\skyman - Copy\conversational-ai\src\ConvAi.BfChannel.BotManagementService\WebJobsExtensionStartup.csConvAi.BfChannel.BotManagementService, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null: 99)
I have logged the config providers in app insights and I can see that there are 3 config providers registered.
1. Microsoft.Extensions.Configuration.ChainedConfigurationProvider
2. Microsoft.Extensions.Configuration.AzureKeyVault.AzureKeyVaultConfigurationProvider
3. Microsoft.Extensions.Configuration.Memory.MemoryConfigurationProvider
Here is problematic code:
config["BotManagementServiceBusConnectionString"] =
config[serviceBusConnectionStringKey].Remove(
config[serviceBusConnectionStringKey].Length - ";EntityPath=Topic1".Length);
Here is the full code:
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WebJobsExtensionStartup.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Linq;
using Intercom.Helpers;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureKeyVault;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
[assembly: WebJobsStartup(typeof(ConvAi.BfChannel.BotManagementService.WebJobsExtensionStartup), "Web Jobs Extension")]
namespace ConvAi.BfChannel.BotManagementService
{
/// <summary>
/// WebJobsExtensionStartup
/// </summary>
public class WebJobsExtensionStartup : IWebJobsStartup
{
/// <summary>
/// AzureServiceTokenProvider which is used for requesting identity token.
/// </summary>
public static AzureServiceTokenProvider AzureServiceTokenProvider { get; set; }
/// <summary>
/// Configure services.
/// </summary>
/// <param name="builder">WebJob Builder</param>
public void Configure(IWebJobsBuilder builder)
{
try
{
bool isLocal = string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID"));
// Gets the default configuration
var serviceConfig = builder.Services.FirstOrDefault(s => s.ServiceType.Equals(typeof(IConfiguration)));
var rootConfig = (IConfiguration)serviceConfig.ImplementationInstance;
var keyVault = rootConfig["BFSpeechKeyVault"];
var serviceBusConnectionStringKey = rootConfig["BotManagementServiceBusConnectionStringKey"];
if (isLocal)
{
// Use developers's idenity.
AzureServiceTokenProvider = new AzureServiceTokenProvider();
}
else
{
// Use azure function's managed idenity.
var msiClientId = rootConfig["MSI_ClientId"];
AzureServiceTokenProvider = new AzureServiceTokenProvider(connectionString: $"RunAs=App;AppId={msiClientId}");
}
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
AzureServiceTokenProvider.KeyVaultTokenCallback));
var config = new ConfigurationBuilder()
.AddConfiguration(rootConfig).AddAzureKeyVault(
keyVault,
keyVaultClient,
new DefaultKeyVaultSecretManager())
.AddInMemoryCollection()
.Build();
config["BotManagementServiceBusConnectionString"] =
config[serviceBusConnectionStringKey].Remove(
config[serviceBusConnectionStringKey].Length - ";EntityPath=Topic1".Length);
// Replace the existing config
builder.Services.AddSingleton<IConfiguration>(config);
}
catch (Exception ex)
{
AppInsights.TrackException(
ex,
"Failed to start Bot management service",
"Sender".PairWith(this.GetType().FullName));
}
}
}
}
Can someone please help me in figuring out what is going on?
I was able to fix the issue by adding the values to the in memory provider.
Config[""] = value
statement tries to add/update the config to every config provider and I could see that ChainedConfigProvider have few providers in the chain which are not registered if they are not being used in the code. ChainedConfigProvider has different set of providers when using locally vs when used in Azure. Due to which statement fails with exception in azure.
Here is the final code:
bool isLocal = string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID"));
// Gets the default configuration
var serviceConfig = builder.Services.FirstOrDefault(s => s.ServiceType.Equals(typeof(IConfiguration)));
var rootConfig = (IConfiguration)serviceConfig.ImplementationInstance;
var keyVault = rootConfig["BFSpeechKeyVault"];
var serviceBusConnectionStringKey = rootConfig["BotManagementServiceBusConnectionStringKey"];
if (isLocal)
{
TelemetryConfiguration.Active.DisableTelemetry = true;
// Use developers's idenity.
AzureServiceTokenProvider = new AzureServiceTokenProvider();
}
else
{
// Use azure function's managed idenity.
var msiClientId = rootConfig["MSI_ClientId"];
AzureServiceTokenProvider = new AzureServiceTokenProvider(connectionString: $"RunAs=App;AppId={msiClientId}");
}
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
AzureServiceTokenProvider.KeyVaultTokenCallback));
var keyVaultConfig = new ConfigurationBuilder()
.AddAzureKeyVault(
keyVault,
keyVaultClient,
new DefaultKeyVaultSecretManager())
.Build();
var botManagementServiceBusConnectionString = Regex.Replace(keyVaultConfig[serviceBusConnectionStringKey], @";EntityPath=Topic1$", string.Empty, RegexOptions.IgnoreCase);
var config = new ConfigurationBuilder()
.AddConfiguration(rootConfig)
.AddInMemoryCollection(
new Dictionary<string, string>
{
{
"BotManagementServiceBusConnectionString",
botManagementServiceBusConnectionString
}
})
.Build();