Using AddAzureKeyVault makes my application 10 seconds slower - azure

I’m having this very simple .NET Core application:
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
builder.AddAzureKeyVault("https://MyKeyVault.vault.azure.net");
var stopwatch = new Stopwatch();
stopwatch.Start();
var configuration = builder.Build();
var elapsed = stopwatch.Elapsed;
Console.WriteLine($"Elapsed time: {elapsed.TotalSeconds}");
}
The csproj-file looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
</ItemGroup>
</Project>
My problem is that the application takes about 10 seconds to execute with a debugger attached (about 5 seconds without a debugger). If I remove the line with AddAzureKeyVault the application is executed in less than a second. I know that AddAzureKeyVault will make the application connect to Azure and read values from a key vault but I expected this to be a lot faster.
Is this an expected behaviour? Is there anything I could do to make this faster?

For the Microsoft.Azure.Services.AppAuthentication library, see the original answer. For the newer Azure.Identity library, see Update 2021-03-22.
Original Answer:
Yes, configure the AzureServiceTokenProvider explicitly to use the az cli for authentication. You can do this by setting an environment variable named AzureServicesAuthConnectionString.
Bash:
export AzureServicesAuthConnectionString="RunAs=Developer; DeveloperTool=AzureCli"
PowerShell:
$Env:AzureServicesAuthConnectionString = "RunAs=Developer; DeveloperTool=AzureCli"
Note that the environment variable needs to be set in whatever session you're running your app.
Explanation
The root of the problem is hinted at in MS docs on authentication, which state, "By default, AzureServiceTokenProvider uses multiple methods to retrieve a token."
Of the multiple methods used, az cli authentication is a ways down the list. So the AzureServiceTokenProvider takes some time to try other auth methods higher in the pecking order before finally using the az cli as the token source. Setting the connection string in the environment variable removes the time you spend waiting for other auth methods to fail.
This solution is preferable to hardcoding a clientId and clientSecret not only for convenience, but also because az cli auth doesn't require hardcoding your clientSecret or storing it in plaintext.
UPDATE (2021-03-22)
The Azure.Identity auth provider, compatible with newer Azure client SDKs (like Azure.Security.KeyVault.Secrets) has code-based options (instead of a connection string) to skip certain authentication methods. You can:
set exclusions in the DefaultAzureCredential constructor, or
generate a TokenCredential using more specific class type constructors (see also the auth provider migration chart here).

You could try to get azure keyvault with clientId and clientSecret and it may run faster.
builder.AddAzureKeyVault("https://yourkeyvaultname.vault.azure.net", clientId,clinetSecret);
And I test with it and it costs 3 seconds.
For more details, you could refer to this article.

The previously suggested solutions with clientId and AzureServiceTokenProvider do have an affect in the deprecated packet Microsoft.Azure.KeyVault. But with the new packet Azure.Security.KeyVault.Secrets these solutions are no longer necessary in my measurements.
My solution is to cache the configuration from Azure KeyVault and store that configuration locally. With this solution you will be able to use Azure KeyVault during development and still have a great performance. This following code shows how to do this:
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
namespace ConfigurationCache
{
public class Program
{
private static readonly Stopwatch Stopwatch = new Stopwatch();
public static void Main(string[] args)
{
Stopwatch.Start();
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((ctx, builder) =>
{
builder.AddAzureConfigurationServices();
})
.ConfigureServices((hostContext, services) =>
{
Stopwatch.Stop();
Console.WriteLine($"Start time: {Stopwatch.Elapsed}");
Console.WriteLine($"Config: {hostContext.Configuration.GetSection("ConnectionStrings:MyContext").Value}");
services.AddHostedService<Worker>();
});
}
public static class AzureExtensions
{
public static IConfigurationBuilder AddAzureConfigurationServices(this IConfigurationBuilder builder)
{
// Build current configuration. This is later used to get environment variables.
IConfiguration config = builder.Build();
#if DEBUG
if (Debugger.IsAttached)
{
// If the debugger is attached, we use cached configuration instead of
// configurations from Azure.
AddCachedConfiguration(builder, config);
return builder;
}
#endif
// Add the standard configuration services
return AddAzureConfigurationServicesInternal(builder, config);
}
private static IConfigurationBuilder AddAzureConfigurationServicesInternal(IConfigurationBuilder builder, IConfiguration currentConfig)
{
// Get keyvault endpoint. This is normally an environment variable.
string keyVaultEndpoint = currentConfig["KEYVAULT_ENDPOINT"];
// Setup keyvault services
SecretClient secretClient = new SecretClient(new Uri(keyVaultEndpoint), new DefaultAzureCredential());
builder.AddAzureKeyVault(secretClient, new AzureKeyVaultConfigurationOptions());
return builder;
}
private static void AddCachedConfiguration(IConfigurationBuilder builder, IConfiguration currentConfig)
{
//Setup full path to cached configuration file.
string path = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MyApplication");
string filename = System.IO.Path.Combine(path, $"configcache.dat");
// If the file does not exists, or is more than 12 hours, update the cached configuration.
if (!System.IO.File.Exists(filename) || System.IO.File.GetLastAccessTimeUtc(filename).AddHours(12) < DateTime.UtcNow)
{
System.IO.Directory.CreateDirectory(path);
UpdateCacheConfiguration(filename, currentConfig);
}
// Read the file
string encryptedFile = System.IO.File.ReadAllText(filename);
// Decrypt the content
string jsonString = Decrypt(encryptedFile);
// Create key-value pairs
var keyVaultPairs = JsonSerializer.Deserialize<Dictionary<string, string>>(jsonString);
// Use the key-value pairs as configuration
builder.AddInMemoryCollection(keyVaultPairs);
}
private static void UpdateCacheConfiguration(string filename, IConfiguration currentConfig)
{
// Create a configuration builder. We will just use this to get the
// configuration from Azure.
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
// Add the services we want to use.
AddAzureConfigurationServicesInternal(configurationBuilder, currentConfig);
// Build the configuration
IConfigurationRoot azureConfig = configurationBuilder.Build();
// Serialize the configuration to a JSON-string.
string jsonString = JsonSerializer.Serialize(
azureConfig.AsEnumerable().ToDictionary(a => a.Key, a => a.Value),
options: new JsonSerializerOptions()
{
WriteIndented = true
}
);
//Encrypt the string
string encryptedString = Encrypt(jsonString);
// Save the encrypted string.
System.IO.File.WriteAllText(filename, encryptedString);
}
// Replace the following with your favorite encryption code.
private static string Encrypt(string str)
{
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(str));
}
private static string Decrypt(string str)
{
return System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(str));
}
}
}

Related

Project level Culture setting for Azure Functions App

Is it possible to change culture on a project level on Azure Functions App?
https://learn.microsoft.com/en-us/azure/azure-functions/functions-app-settings
The app is using Consumption plan or Premium plan, not via ASP.NET Core.
My Startup.cs file is like below:
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
}
}
Can ASP.NET Core that is based on different Startup.cs not like above use Consumption plan or Premium plan??
Asp.net Core that must use App Service plan like below:
https://andrewlock.net/adding-localisation-to-an-asp-net-core-application/
When migrating legacy application running on servers to Azure you always need to take care of Timezone and Culture settings that originally are fetched from the machine. For Azure Functions you can set the timezone in the app settings:
WEBSITE_TIME_ZONE=Europe/London
Possible values found here https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. This actually differs for app services that seem to use TZ for Linux and WEBSITE_TIME_ZONE for Windows.
For culture it is more complicated. Using aspnet core you define it in Configure in the Startup class
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
ILoggerFactory loggerFactory)
{
var cultureInfo = new CultureInfo("en-US");
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(cultureInfo),
SupportedCultures = new List<CultureInfo>
{
cultureInfo,
},
SupportedUICultures = new List<CultureInfo>
{
cultureInfo,
}
});
}
That is not possible in Azure Function Apps. What you can do is to create a Setup class and then set the culture for the appdomain and the current thread. This will probably work as long Azure isnt altering the Appdomain.
[assembly: FunctionsStartup(typeof(Startup))]
namespace FunctionApp
{
public class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder app)
{
var cultureInfo = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
}
}
}
Azure Function didn't provide a built-in method to change culture.
Put this at the starting of your function app:
using System.Threading;
using System.Globalization;
//......
string culture = "en-US";
CultureInfo CI = new CultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = CI;
Above code will change the culture to en-US. You can set it to other value.
This is the document:
https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.currentculture?view=netcore-3.1
Does this solved your problem?

Net core Key vault configuration using Azure.Security.KeyVault.Secrets

I have found out it is easy to connect to Azure KeyVault using Managed Identity. The documentation shows how to do it :
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(
$"https://{builtConfig["KeyVaultName"]}.vault.azure.net/",
keyVaultClient,
new DefaultKeyVaultSecretManager());
Then I realized it requires the package Microsoft.Azure.KeyVault which is deprecated. So I'm struggling to figure out how to do the above with SDK 4. All the documentation I find is related to SDK 3.
[EDIT]
I have found out the following code works to get the azure KeyVault Secret using Managed Identiy with SDK 4. However I can't see how to add this to my configuration. It used to be done with config.AddAzureKeyVault() from the Microsoft.Extensions.Configuration.AzureKeyVault Package however it is not compatible with the SDK 4 SecretClient:
return Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var azureCredentialOptions = new DefaultAzureCredentialOptions();
var credential = new DefaultAzureCredential(azureCredentialOptions);
var secretClient = new SecretClient(new System.Uri("https://mykeyvault.vault.azure.net/"), credential);
var secret = secretClient.GetSecret("StorageConnectionString");
config.AddAzureKeyVault()
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
As it turned out, I found the proper way to do it with SDK 4. I had to install the package azure.extensions.aspnetcore.configuration.secrets and then the code is simply :
var credential = new DefaultAzureCredential();
config.AddAzureKeyVault(new System.Uri("https://mykv.vault.azure.net/"), credential);
then to use it
configuration["StorageConnectionString"]
AS per June 2020
First thing is that Microsoft.Azure.KeyVault is not deprecated but replaced. Using the old nuget package is still a valid option.
I imagine in the future, the Microsoft.Extensions.Configuration.AzureKeyVault nuget package will use the new Azure.Security.KeyVault.Secrets package.
In my experience I would stick with the existing library and wait for future updates.
If you really want to use the Azure.Security.KeyVault.Secrets, you can implement your own custom configuration builder.
I had a look at the existing key vault configuration code on github and here is a simplified/modified version that you could use.
First install these nuget packages Azure.Identity and Azure.Security.KeyVault.Secrets.
The new key vault secrets package uses IAsyncEnumerable so you need to update your project to target C#8.0: update you csproj file with <LangVersion>8.0</LangVersion>.
Azure Key Vault Secret configuration code:
public interface IKeyVaultSecretManager
{
bool ShouldLoad(SecretProperties secret);
string GetKey(KeyVaultSecret secret);
}
public class DefaultKeyVaultSecretManager : IKeyVaultSecretManager
{
public bool ShouldLoad(SecretProperties secret) => true;
public string GetKey(KeyVaultSecret secret)
=> secret.Name.Replace("--", ConfigurationPath.KeyDelimiter);
}
public class AzureKeyVaultConfigurationProvider : ConfigurationProvider
{
private readonly SecretClient _client;
private readonly IKeyVaultSecretManager _manager;
public AzureKeyVaultConfigurationProvider(SecretClient client, IKeyVaultSecretManager manager)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_manager = manager ?? throw new ArgumentNullException(nameof(manager));
}
public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();
private async Task LoadAsync()
{
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
await foreach (var secretProperties in _client.GetPropertiesOfSecretsAsync())
{
if (!_manager.ShouldLoad(secretProperties) || secretProperties?.Enabled != true)
continue;
var secret = await _client.GetSecretAsync(secretProperties.Name).ConfigureAwait(false);
var key = _manager.GetKey(secret.Value);
Data.Add(key, secret.Value.Value);
}
Data = data;
}
}
public class AzureKeyVaultConfigurationSource : IConfigurationSource
{
public SecretClient Client { get; set; }
public IKeyVaultSecretManager Manager { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new AzureKeyVaultConfigurationProvider(Client, Manager);
}
}
public static class AzureKeyVaultConfigurationExtensions
{
public static IConfigurationBuilder AddAzureKeyVault(
this IConfigurationBuilder configurationBuilder,
SecretClient client,
IKeyVaultSecretManager manager = null)
{
if (configurationBuilder == null)
throw new ArgumentNullException(nameof(configurationBuilder));
if (client == null)
throw new ArgumentNullException(nameof(client));
configurationBuilder.Add(new AzureKeyVaultConfigurationSource()
{
Client = client,
Manager = manager ?? new DefaultKeyVaultSecretManager()
});
return configurationBuilder;
}
}
You can now use this configuration builder in your project like that:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var azureCredentialOptions = new DefaultAzureCredentialOptions();
var credential = new DefaultAzureCredential(azureCredentialOptions);
var secretClient = new SecretClient(new System.Uri("https://mykeyvault.vault.azure.net/"), credential);
config.AddAzureKeyVault(secretClient);
})
.UseStartup<Startup>();
}
For your info, if you're using Azure Key Vault secrets in your App Service or Azure Functions application settings, you don't have to add extra code to get the key vault value.
You just need to change your app settings values (in azure portal), with your key vault references.
For the steps, look at https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references
Setting example using key vault references:
{
"name": "DatabaseSettings:ConnectionString",
"value": "#Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/DatabaseConnectionSettingSecret/ec96f02080254fxxxxxxxxxxxxxxx)",
"slotSetting": false
}
But this doesn't work for a local development, instead you should use plain secret value. But for me it's okay, because your local development uses different secret value and you don't add your local.settings.json to source control.
Latest working solution with version 4 library. My stack is .netcore 3.1 and I am using it inside an Azure Web App to access a secret from Azure KeyVault.
First thing first - go through this MS Doc link
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
//..........
var kvUri = "https://YOURVAULTNAME.vault.azure.net/";
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
KeyVaultSecret secret = client.GetSecret("SECRETNAME");
// Can also use await.....GetSecretAsync()
this.ConnectionString = secret.Value.ToString();
\\thats my internal variable, secret.Value.ToString() is required value
I assume here that you have
Created keyVault in Azure
created a Managed identity in App Service(Using 'Identity' option in app service)
Added above identity in Azure KeyVault('Access policies' option)
I am using something like this,
var keyVaultEndpoint = GetKeyVaultEndpoint();
if (!string.IsNullOrEmpty(keyVaultEndpoint))
{
// Pass appropriate connection string
var azureServiceTokenProvider = new
AzureServiceTokenProvider(certThumbprintConnectionString);
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(keyVaultEndpoint, keyVaultClient, new DefaultKeyVaultSecretManager());
}
private static string GetKeyVaultEndpoint() => "https://<<key-vault-name>>.vault.azure.net";

How to read key vault secrets through IConfiguration object in local development in Azure web app

I've created a service principal for my local development through the AzureServicesAuthConnectionString environment variable and granted that principal access to the keyvault.
However, when I read configuration["secret"] for a secret in key vault, it is null, and if I inspect the providers in the configuration object, I don't see a keyvault provider, only json providers for my appsettings files. Is there a step that I'm missing?
I create a .net core webapp and test well in my site. Here is the code you could refer to.
In Program.cs:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder =>
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient =new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
builder.AddAzureKeyVault("https://yourkeyvaultname.vault.azure.net/",
keyVaultClient, new DefaultKeyVaultSecretManager());
})
.UseStartup<Startup>();
In HomeController.cs:
private readonly IConfiguration _configuration;
public HomeController(IConfiguration configuration)
{
_configuration = configuration;
}
public IActionResult Index()
{
ViewBag.Secret = _configuration["yoursecretname"];
return View();
}
The snapshot:
For more details, you could refer to this article.
You can try the new feature we have to just pump the secret into the App Settings directly.
Here is the blog to show how to set that up. It is easy and works via Template deployments as well.
https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references

How to get notification when webjob status was aborted in azure

Azure WebJob, how to be notified if it aborted?
(1)Always Availability is on for the service.
(2) SCM_COMMAND_IDLE_TIMEOUT = 2000.
WEBJOBS_IDLE_TIMEOUT = 2000.
But as i'm new to this. can you please help me on this one where can i put the logic
You could add the logic in the Function.cs file. For more information you could refer to the detail steps
Steps:
1.follow official document to create a webjob project.
2.Add Functions.cs in the project
using System.IO;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions;
using SendGrid;
public class Functions
{
//demo webjob trigger
public static void ProcessQueueMessage([QueueTrigger("queue")] string message, TextWriter log)
{
log.WriteLine(message);
}
// error monitor
public static void ErrorMonitor([ErrorTrigger("0:30:00", 10, Throttle = "1:00:00")]TraceFilter filter, [SendGrid] SendGridMessage message)
{
message.Subject = "WebJobs Error Alert";
message.Text = filter.GetDetailedMessage(5);
}
}
3.If want to use ErrorTrigger and SendGrid we need to config it in the Program.cs file.
static void Main()
{
var config = new JobHostConfiguration();
if (config.IsDevelopment)
{
config.UseDevelopmentSettings();
}
config.UseCore();
config.UseSendGrid(new SendGridConfiguration
{
ApiKey = "xxxxx",
FromAddress = new Email("emailaddress","name"),
ToAddress = new Email("emailaddress","name")
});
var host = new JobHost(config);
// The following code ensures that the WebJob will be running continuously
host.RunAndBlock();
}
4.If we want to test it locally, we need to add Storage connection string in the App Settings collection.
<connectionStrings>
<add name="AzureWebJobsStorage" connectionString="{storage connection string}" />
</connectionStrings>

ASP.NET Core How to read the content of an AzureTable

I develop a ASP.NET Core application working with Azure Tables.
So, I created a tables storage account in Azure Portal, created a table, filled it with some test data, and now I would like to display the content of that table to test the reading.
my appsettings.json is
{
"ConnectionStrings": {
"MyTables":"DefaultEndpointsProtocol=https;AccountName=yyy;AccountKey=xxx;EndpointSuffix=core.windows.net"
},
"Logging": {
"IncludeScopes": false,
[etc etc...]
}
}
And my Startup.cs:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
// here in debug we can see the connection string, that is OK
Console.WriteLine($"conn string:{Configuration["ConnectionStrings:MyTables"]}");
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}
And here is my controller I try to Display the values:
using Microsoft.AspNetCore.Mvc;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using NextMove.Models;
using System.Text;
[...]
public class HelloWorldController : Controller
{
public string ReadTables() {
// ????? Code does not work, as Startup not a reference
string myConnString = Startup.Configuration["ConnectionStrings:MyTables"];
//////////////////////////////////
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(myConnString);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("themes");
TableQuery<ProjectThemeEntity> query = new TableQuery<ProjectThemeEntity>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "fr"));
StringBuilder response = new StringBuilder("Here is your test table:");
foreach (ProjectThemeEntity item in table.ExecuteQuery(query)) {
response.AppendLine($"Key: {item.RowKey}; Value: {item.Description}");
}
return response.ToString();
}
//
// GET: /HelloWorld/
public IActionResult Index() {
return View();
}
Questions:
a) How to fix this code in order to get the connection string?
b) There should be a "Table.ExecuteQuery(query)" as per this MSDN article in the controller's foreach, but it does not find such a method in CloudTable class, I however added the necessary references, as shown in the controller's code above, only two "Async" methods are available:
PS.
-For the (b) question several people has the same issue here, hope the situation changed now...
You can't access Startup.Configuration from the controller because it's not a static property. Even though you've made it public (generally not a good idea) it still requires you to have an instance of Startup to get access to it.
Generally to get access to settings in ASP.NET Core it's best to create a class with the properties you want and use the IOptions pattern to get them with Dependency Injection. In your startup where you configure your services (add services to the dependency injection container) you would use the helper methods to add your configuration object to the container and then in your controller you would specify you wanted an IOptions or IOptionsSnapshot to get access to it.
I'd suggest you don't put your data access in your controller though. It makes your controller harder to read and harder to maintain if you need to change your strategy later. Move your ReadTables method to its own class and add it to the DI container in Startup taking whatever settings you need to create the service. Use constructor injection in your controller to get the service and execute calls from your controller actions where you need them.

Resources