Sorrow, Rage and Despair setting up Azure storage - azure

I'm trying to use Azure storage locally. I have a data source class called ExpenseDataSource:
public class ExpenseDataSource
{
private static CloudStorageAccount storageAccount;
private ExpenseTableContext context;
static ExpenseDataSource()
{
//CloudStorageAccount.SetConfigurationSettingPublisher(
// (configName, configSettingPublisher) =>
// {
// string connectionString = RoleEnvironment.GetConfigurationSettingValue(configName);
// configSettingPublisher(connectionString);
// }
//);
storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
CloudTableClient.CreateTablesFromModel(typeof(ExpenseTableContext), storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
}
public ExpenseDataSource()
{
context = new ExpenseTableContext(storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
context.RetryPolicy = RetryPolicies.Retry(3, TimeSpan.FromSeconds(1));
}
public IEnumerable<ExpenseInfo> Select()
{
var results = from g in context.Expenses
where g.PartitionKey == "Expense"
select g;
return results;
}
// ...
}
(I'm new to Azure, so this class could be sub-optimal in many ways.)
When I try to create an object of type ExpenseDataSource, the following exception occurs:
System.TypeInitializationException: The type initializer for 'WebRole1.ExpenseDataSource' threw an exception. ---> System.InvalidOperationException: SetConfigurationSettingPublisher needs to be called before FromConfigurationSetting can be used
at Microsoft.WindowsAzure.CloudStorageAccount.FromConfigurationSetting(String settingName)
at WebRole1.ExpenseDataSource..cctor() in [ ... ]
--- End of inner exception stack trace ---
at WebRole1.ExpenseDataSource..ctor()
at WebRole1.ExpenseService.WebRole1.IExpenseService.GetExpenses() in [ ... ]
However, this is odd, because SetConfiguationSettingPublisher has already been called:
public class WebRole : RoleEntryPoint
{
public override bool OnStart()
{
DiagnosticMonitor.Start("DiagnosticsConnectionString");
// For information on handling configuration changes
// see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
RoleEnvironment.Changing += RoleEnvironmentChanging;
CloudStorageAccount.SetConfigurationSettingPublisher(
(configName, configSettingPublisher) =>
{
string connectionString = RoleEnvironment.GetConfigurationSettingValue(configName);
configSettingPublisher(connectionString);
}
);
return base.OnStart();
}
// ...
}
I am able to hit breakpoints here when I start debugging.
What am I doing wrong here?
Update: I thought that maybe I'd started the dev fabric and ASP.NET localhost out of order, so I killed them both, launched the dev fabic, then launched the ASP project. Still no luck - the same error occurs.
Update 2: I changed my OnStart() to this, but it still doesn't work:
public override bool OnStart()
{
DiagnosticMonitor.Start("DiagnosticsConnectionString");
// For information on handling configuration changes
// see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
RoleEnvironment.Changing += RoleEnvironmentChanging;
#region Setup CloudStorageAccount Configuration Setting Publisher
// This code sets up a handler to update CloudStorageAccount instances when their corresponding
// configuration settings change in the service configuration file.
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
{
// Provide the configSetter with the initial value
configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
RoleEnvironment.Changed += (sender, arg) =>
{
if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>()
.Any((change) => (change.ConfigurationSettingName == configName)))
{
// The corresponding configuration setting has changed, propagate the value
if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
{
// In this case, the change to the storage account credentials in the
// service configuration is significant enough that the role needs to be
// recycled in order to use the latest settings. (for example, the
// endpoint has changed)
RoleEnvironment.RequestRecycle();
}
}
};
});
#endregion
return base.OnStart();
}
Update 3: I tried putting the "Setup CloudStorageAccount Configuration Setting Publisher" region in the ExpenseDataSource static initializer, and got the following error:
System.TypeInitializationException: The type initializer for 'WebRole1.ExpenseDataSource' threw an exception. ---> System.Runtime.InteropServices.SEHException: External component has thrown an exception.
at RoleEnvironmentGetConfigurationSettingValueW(UInt16* , UInt16* , UInt32 , UInt32* )
at Microsoft.WindowsAzure.ServiceRuntime.Internal.InteropRoleManager.GetConfigurationSetting(String name, String& ret)
at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.GetConfigurationSettingValue(String configurationSettingName)
at WebRole1.ExpenseDataSource.<.cctor>b__0(String configName, Func`2 configSetter) in C:\Users\ODP\Documents\Visual Studio 2010\Projects\ExpenseCalc\WebRole1\ExpenseDataSource.cs:line 26
at Microsoft.WindowsAzure.CloudStorageAccount.StorageAccountConfigurationSetting..ctor(String configurationSettingName)
at Microsoft.WindowsAzure.CloudStorageAccount.FromConfigurationSetting(String settingName)
at WebRole1.ExpenseDataSource..cctor() in C:\Users\ODP\Documents\Visual Studio 2010\Projects\ExpenseCalc\WebRole1\ExpenseDataSource.cs:line 47
--- End of inner exception stack trace ---
at WebRole1.ExpenseDataSource..ctor()
at WebRole1.ExpenseService.WebRole1.IExpenseService.GetExpenses() in C:\Users\ODP\Documents\Visual Studio 2010\Projects\ExpenseCalc\WebRole1\ExpenseService.svc.cs:line 18
Update 3: Following smarx's suggestion, I changed the static initializer:
static ExpenseDataSource()
{
//storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
storageAccount = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("DataConnectionString"));
CloudTableClient.CreateTablesFromModel(typeof(ExpenseTableContext), storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
}
This leads to the following error:
System.TypeInitializationException: The type initializer for 'WebRole1.ExpenseDataSource' threw an exception. ---> System.Runtime.InteropServices.SEHException: External component has thrown an exception.
at RoleEnvironmentGetConfigurationSettingValueW(UInt16* , UInt16* , UInt32 , UInt32* )
at Microsoft.WindowsAzure.ServiceRuntime.Internal.InteropRoleManager.GetConfigurationSetting(String name, String& ret)
at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.GetConfigurationSettingValue(String configurationSettingName)
at WebRole1.ExpenseDataSource..cctor() in C:\Users\ODP\Documents\Visual Studio 2010\Projects\ExpenseCalc\WebRole1\ExpenseDataSource.cs:line 20
--- End of inner exception stack trace ---
at WebRole1.ExpenseDataSource..ctor()
at WebRole1.ExpenseService.WebRole1.IExpenseService.GetExpenses() in C:\Users\ODP\Documents\Visual Studio 2010\Projects\ExpenseCalc\WebRole1\ExpenseService.svc.cs:line 18
The error is slightly different from above. Could this be related to the idea that I'm somehow not actually running ASP.NET within the dev fabric?
Ugh. I'm starting to miss Google App Engine storage's simple get() and put() interface.

1) Make sure that "DataConnectionString" is configured in your settings of WebRole.
In your Solution Explorer --> Under the "Roles" folder --> Right-click on | Properties --> Go to Settings tab and click "Add Setting". Enter Name: "DataConnectionString"; Type:"ConnectionString"; Value:"UseDevelopmentStorage=true" (if you want to debug and use local storage) or if you are planning to migrate to Azure-enter your storage account details.
2) (In the above code - Remove the comment for SetConfigurationSettingPublisher). Your code should look like this:
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
{
configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
});
var storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");

I can think of two reasons:
You are using Azure SDK 1.3 and the SetConfigurationSettingPublisher must be called in your Global.asax.cs Application_Start;
You are not setting the Startup project as the *.CloudService one.

If you're still having problems, try actually selecting the Web Role under the Cloud Project and starting debugging from there, that has worked for me when I've had issues with other methods.

Had the same problem, I didn't had the Azure project as start-up project.
As Muhammad Omar mention in his comment on the question, see this related question as well.

Related

Filter out successful dependencies from AppInsight

I have created the following TelemetryFilter:
public class TelemetryFilter : ITelemetryProcessor
{
private ITelemetryProcessor Next { get; set; }
public TelemetryFilter(ITelemetryProcessor next)
{
Next = next;
}
public void Process(ITelemetry item)
{
var dependency = item as DependencyTelemetry;
if (dependency != null && dependency.Success == true) return;
Next.Process(item);
}
}
And added TelemetryFilter to TelemetruyProcessors in ApplicationInsights.config. It works when I run the application on my machine but when it is deployed to test and production environments, dependencies are getting collected by Azure AppInsights. When I see them in Azure Portal they have the property Call status: true. Is Call status refers to dependency.Success? What's the best way to filter out all successful calls to decrease our AppInsights data ingress and lower our Azure bill?
Filter out all successful dependencies:
you can initialize the filter in code. In a suitable initialization class,
AppStart in Global.asax.cs, insert your processor into the chain:
var builder = TelemetryConfiguration.Active.DefaultTelemetrySink.TelemetryProcessorChainBuilder;
builder.Use((next) => new SuccessfulDependencyFilter(next));
// If you have more processors:
builder.Use((next) => new AnotherProcessor(next));
builder.Build();
Refer for filtering sampling
& for request filtering
To Reduce Application Insights cost
You need to optimize Telemetry with Application Insights check here
Check here for some more methods to reduce Application insights cost
I found that ApplicationInsights.config file wasn't set to be copied into the output folder by the build process. That's why it didn't work.

.NET Core API will not work on Azure App Service-The resource you are looking for has been removed,had its name changed,or is temporarily unavailable

I've deployed my API to an Azure App Service and get the error:
The resource you are looking for has been removed,had its name changed,or is temporarily unavailable.
any time I try to hit the endpoint of the only current operation in the API. All of the files have deployed correctly in the wwwroot folder and if I enter url/filename where url is the base url and filename is any of the files in the folder, I am able to download the file. The API works when run locally, hitting the operation returns the expected json result.
Running a trace gives the rather generic result:
System.NullReferenceException 2
Object reference not set to an instance of an object.
Stack Trace 1
mscorlib!System.Diagnostics.Tracing.EventSource.SendCommand
mscorlib!System.Diagnostics.Tracing.EventSource+OverideEventProvider.OnControllerCommand
mscorlib!System.Diagnostics.Tracing.EventProvider.EtwEnableCallBack
mscorlib!dynamicClass.IL_STUB_ReversePInvoke
The routes are configured correctly (in that it works locally) - the error implies that a related file is missing, however checking the folder in Kudu shows the files match the contents of the bin folder. Any ideas on what is going wrong here? Or how to determine what the missing resource is? Thanks for reading.
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddLogging(logging =>
{
logging.AddConsole();
logging.AddDebug();
});
services.AddDbContext<hidden>(options => options.UseSqlServer(Configuration.GetConnectionString("hidden")));
services.AddScoped<DbContext, hidden>();
services.AddScoped(typeof(IQuery<>), typeof(NoTrackingQuery<>));
services.AddScoped(typeof(IQuery<,>), typeof(NoTrackingQuery<,>));
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Hidden", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Expose logging where DI cannot be used
var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
LogManager.SetLogger(loggerFactory);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Hidden v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
[ApiController]
[Route("[controller]")]
public class WidgetController : ControllerBase
{
private readonly IQuery<Category> _categories;
public WidgetController(IQuery<Widget> widgets)
{
_widgets = widgets;
}
[HttpGet]
public IEnumerable<Widget> Get()
{
return _widgets.QueryAll();
}
}
When you can run a solution locally, and not able to run it on Cloud, it means that you have misconfigured something.
Looking at the error message I suspect that the settings for Logging are not in place. Make sure that you put all required/consumed settings in Application Settings or Connection Strings.
Thanks singhh-msft. You were right and this was caused by using the incorrect publish task in the build pipeline. Updated to use dotnet Azure CLI publish command and the issue is resolved.

How can I view console or trace output in an azure app service? Console.WriteLine or Trace.TraceError

I thought this would be simple but after several hours have realized that it is not. I deployed an "App Service" to Azure that is a C# .Net Core.
I am trying to add some crude monitoring of by app by using Console.WriteLine("my message") or Trace.TraceError("my message") but I can't find that output anywhere in Azure.
I tried enabling Application Insights but didn't find the output there either.
I just want the simplest way to get a little idea that a line of code in my app is getting hit. What is that simplest way?
Things I have tried:
1) I went in to the console in Azure and browsed to every file that might have this sort of output but did not find any. Certainly nothing in any of the files and folders under LogFiles.
2) I DID go to "App Service Logs" and enabled "Application Logging (Filesystem)".
3) I also enabled "Detailed error messages" in "App Service Logs".
4) I tried "Diagnostic settings (preview)" but could not find the output there.
5) I watch the "Log Stream" for Application Logs but nothing shows up there
6) Under "Logs" I just have this message: "We didn’t find any logs"
7) Neither "metrics" nor "Alerts" has this log output
I am beginning to suspect this is not support in Azure. Do I need to add a logging framework like serilog just to one time add a statement to may app like "You hit line 20"? I really just want something quick and dirty, but after spending a few hours on this the "quick" part of it isn't happening.
What I did was just to enable the App Service Logs in the Azure Portal.
Specifically I turned on the "Application Logging (Filesystem)" at the "Verbose" Level and then I selected "File System" in the "Web server logging" option
Then you can use the "Log stream" in the left bar to display the application logs.
In your code you just have to call
System.Diagnostics.Trace.TraceInformation("My message!")
Or
System.Diagnostics.Trace.WriteLine("My message!")
And that's it!
Here I include some screenshots:
I finally figured it out myself. I needed to add configuring AzureFileLoggerOptions. Without those options, nothing showed up in the Azure output.
.ConfigureLogging(logging => logging.AddAzureWebAppDiagnostics())
.ConfigureServices(serviceCollection => serviceCollection
.Configure<AzureFileLoggerOptions>(options =>
{
options.FileName = "azure-diagnostics-";
options.FileSizeLimit = 50 * 1024;
options.RetainedFileCountLimit = 5;
}).Configure<AzureBlobLoggerOptions>(options =>
{
options.BlobName = "log.txt";
})
)
In summary, for .Net Core 3.1, to get your messages to show up in the "Log Stream", you need to enable "Application Logging" in the "App Service Logs" section of Azure. In your code you need to reference:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.AzureAppServices;
Also, in my case, I am using Blazor/SignalR and don't have an MVC Controller, so I couldn't figure out how to access the Logging framework in my class. I am sure there is a better way, but by exposing the ILogger as in the code below I was able to reference it from anywhere within my code base.
I have a static method I call to write to the log (console when I am actively debugging on my pc, or Azure "Log Stream" when running in Azure:
public static void WriteToLog(string message)
{
Program.Logger.LogError(message);
}
Code in my Program.cs looks like this:
public class Program
{
public static ILogger Logger;
public static void Main(string[] args)
{
//CreateHostBuilder(args).Build().Run();
var host = CreateHostBuilder(args).Build();
var LoggerF = LoggerFactory.Create(builder =>
{
builder.AddFilter("BlazorDiceRoller", LogLevel.Warning)
.AddConsole().AddAzureWebAppDiagnostics()
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning);
});
Logger = LoggerF.CreateLogger<Program>();
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.AddAzureWebAppDiagnostics())
.ConfigureServices(serviceCollection => serviceCollection
.Configure<AzureFileLoggerOptions>(options =>
{
options.FileName = "azure-diagnostics-";
options.FileSizeLimit = 50 * 1024;
options.RetainedFileCountLimit = 5;
}).Configure<AzureBlobLoggerOptions>(options =>
{
options.BlobName = "log.txt";
})
)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
You can open Kudu and view Console.WritLine output. Open it from portal or:
https://your_app_name_on_azure.scm.azurewebsites.net/api/logstream
Don't forget to enable logs in azure:
Yes, this(console or trace output) is not supported in .NET core only. You should take use of ILogger as per steps below.
In Startup.cs -> Configure method, re-write Configure method like below:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//your other code
//add the following 2 lines of code.
loggerFactory.AddConsole();
loggerFactory.AddDebug();
app.UseStaticFiles();
//your other code
}
Then in HomeController.cs(for example), add the following code:
public class HomeController : Controller
{
private readonly ILogger _logger;
public HomeController(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<HomeController>();
}
public IActionResult Index()
{
_logger.LogInformation("this is a information from ILogger...");
return View();
}
//other code
}
Please let me know if you still have more issues.
Try to use the namespace System.Diagnostics.Trace to register info about diagnostic
eg:
System.Diagnostics.Trace.TraceError("If you're seeing this, something bad happened");
By Default, ASP.NET Core use the provider for logs this namespace: Microsoft.Extensions.Logging.AzureAppServices.

Application Insights telemetry correlation using log4net

I'm looking to have our distributed event logging have proper correlation. For our Web Applications this seems to be automatic. Example of correlated logs from one of our App Services API:
However, for our other (non ASP, non WebApp) services were we use Log4Net and the App Insights appender our logs are not correlated. I tried following instructions here: https://learn.microsoft.com/en-us/azure/azure-monitor/app/correlation
Even after adding unique operation_Id attributes to each operation, we're not seeing log correlation (I also tried "Operation Id"). Example of none correlated log entry:
Any help on how to achieve this using log4net would be appreciated.
Cheers!
Across services correlation IDs are mostly propagated through headers. When AI is enabled for Web Application, it reads IDs/Context from the incoming headers and then updates outgoing headers with the appropriate IDs/Context. Within service, operation is tracked with Activity object and every telemetry emitted will be associated with this Activity, thus sharing necessary correlation IDs.
In case of Service Bus / Event Hub communication, the propagation is also supported in the recent versions (IDs/Context propagate as metadata).
If service is not web-based and AI automated correlation propagation is not working, you may need to manually get incoming ID information from some metadata if any exists, restore/initiate Activity, start AI operation with this Activity. When telemetry item is generated in scope of that Activity, it will get proper IDs and will be part of the overarching trace. With that, if telemetry is generated from Log4net trace that was executed in the scope of AI operation context then that telemetry should get right IDs.
Code sample to access correlation from headers:
public class ApplicationInsightsMiddleware : OwinMiddleware
{
// you may create a new TelemetryConfiguration instance, reuse one you already have
// or fetch the instance created by Application Insights SDK.
private readonly TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.CreateDefault();
private readonly TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration);
public ApplicationInsightsMiddleware(OwinMiddleware next) : base(next) {}
public override async Task Invoke(IOwinContext context)
{
// Let's create and start RequestTelemetry.
var requestTelemetry = new RequestTelemetry
{
Name = $"{context.Request.Method} {context.Request.Uri.GetLeftPart(UriPartial.Path)}"
};
// If there is a Request-Id received from the upstream service, set the telemetry context accordingly.
if (context.Request.Headers.ContainsKey("Request-Id"))
{
var requestId = context.Request.Headers.Get("Request-Id");
// Get the operation ID from the Request-Id (if you follow the HTTP Protocol for Correlation).
requestTelemetry.Context.Operation.Id = GetOperationId(requestId);
requestTelemetry.Context.Operation.ParentId = requestId;
}
// StartOperation is a helper method that allows correlation of
// current operations with nested operations/telemetry
// and initializes start time and duration on telemetry items.
var operation = telemetryClient.StartOperation(requestTelemetry);
// Process the request.
try
{
await Next.Invoke(context);
}
catch (Exception e)
{
requestTelemetry.Success = false;
telemetryClient.TrackException(e);
throw;
}
finally
{
// Update status code and success as appropriate.
if (context.Response != null)
{
requestTelemetry.ResponseCode = context.Response.StatusCode.ToString();
requestTelemetry.Success = context.Response.StatusCode >= 200 && context.Response.StatusCode <= 299;
}
else
{
requestTelemetry.Success = false;
}
// Now it's time to stop the operation (and track telemetry).
telemetryClient.StopOperation(operation);
}
}
public static string GetOperationId(string id)
{
// Returns the root ID from the '|' to the first '.' if any.
int rootEnd = id.IndexOf('.');
if (rootEnd < 0)
rootEnd = id.Length;
int rootStart = id[0] == '|' ? 1 : 0;
return id.Substring(rootStart, rootEnd - rootStart);
}
}
Code sample for manual correlated operation tracking in isolation:
async Task BackgroundTask()
{
var operation = telemetryClient.StartOperation<DependencyTelemetry>(taskName);
operation.Telemetry.Type = "Background";
try
{
int progress = 0;
while (progress < 100)
{
// Process the task.
telemetryClient.TrackTrace($"done {progress++}%");
}
// Update status code and success as appropriate.
}
catch (Exception e)
{
telemetryClient.TrackException(e);
// Update status code and success as appropriate.
throw;
}
finally
{
telemetryClient.StopOperation(operation);
}
}
Please note that the most recent version of Application Insights SDK is switching to W3C correlation standard, so header names and expected format would be different as per W3C specification.

Web app running on Azure is not picking up and using an environment variable connection string (ASP,NET Core RC1)

Github link for reproduction.
I have an ASP.NET Core (RC1) application that works fine locally. The issue I'm having is that my connection string is not being picked up by my Azure app. I've asked similar questions to this, but I've narrowed down the issue on my end in this app. Note, it requires an app on Azure to reproduce it.
Here's the issue I'm seeing.
First, my configuration is setup as such:
public Startup()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("config.json")
.AddEnvironmentVariables();
mConfiguration = builder.Build();
}
And EF7 is setup here:
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<FooDbContext>(options =>
{
// I'm assuming it's failing here.
// I'm not sure how to debug it running on Azure.
// All the developer exception page shows is:
// 500 Internal Server Error "An error occurred while starting the application."
options.UseSqlServer(mConfiguration["Data:ConnectionStringTest:ConnectionString"]);
});
services.AddScoped<IFooDataService, FooSqlDataService>();
}
My config.json has:
{
"Data": {
"ConnectionStringTest": {
"ConnectionString": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=ConnectionStringTest"
}
}
}
And this should be overridden by the connection string I've setup in Azure:
When going to the Kudu SCM and looking at the environment variables on the Azure web app instance, I see the following:
SQLAZURECONNSTR_Data:ConnectionStringTest:ConnectionString = my_connection_string_here
I am assuming this is the class that is being used under the hood when my environment variable is used at runtime: EnvironmentVariablesConfigurationProvider
Ok here's what I found, and this feels awkward.
It seems that you need to use Data:{my_connection_string_key}:ConnectionString everywhere EXCEPT in Azure. This environment variable converter will construct the proper connection string using this format automatically if the connection string is prefixed with SQLAZURECONNSTR_.
This means when you setup your connection string in Azure, you need to omit EVERYTHING except the key to your connection string. Do not insert Data: or :ConnectionString... simply use {connection_string_key} (refer to the above format) instead. If you include the entire format in your Azure key/value pair, the EnvironmentVariablesConfigurationProvider will add another Data: and :ConnectionString around it, resulting in something like Data:Data:{my_connection_string_key}:ConnectionString:ConnectionString.
In ConfigureServices(...), use the format that ASP expects:
... options.UseSqlServer(mConfiguration["Data:ConnectionStringTest:ConnectionString"]);
You can therefore locally use this for your local JSON, for testing in development/fallback:
{
"Data": {
"ConnectionStringTest": {
"ConnectionString": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=ConnectionStringTest"
}
}
}
Just make sure your Azure connection string has the middle part of that format (ConnectionStringTest using this example).
This will make your environment variable in Azure look like this in raw format:
SQLAZURECONNSTR_ConnectionStringTest = {insert connection string here}
And the EnvironmentVariablesConfigurationProvider will strip off the Azure prefix string, and wrap your key in the hardcoded format: Data:{0}:ConnectionString
Experiment Results
To augment your excellent answer, I did a local experiment to confirm that the SQLAZURECONNSTR_connection_string_key environmental variable becomes this configuration:
mConfiguration["Data:connection_string_key:ConnectionString"]
Local Experiment
A local environmental variable emulates an Azure SQL Database connection string named connection_string_key.
PS> $env:SQLAZURECONNSTR_connection_string_key = "an azure conn string"
The following code dumps all the environmental variables and configuration sections to the page.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Run(async (context) =>
{
await context.Response.WriteAsync("# Environmental Variables \r\n");
await DumpAllEnvVariables(context, Environment.GetEnvironmentVariables());
await context.Response.WriteAsync("# Configuration Sections \r\n");
await DumpAllConfigItems(context, mConfiguration.GetChildren());
});
}
private async Task DumpAllEnvVariables(HttpContext context, IDictionary envVariables)
{
foreach (var envVar in envVariables.Cast<DictionaryEntry>())
{
await context.Response.WriteAsync($"{envVar.Key}"); // : {envVar.Value}
await context.Response.WriteAsync($"\r\n");
}
}
private async Task DumpAllConfigItems(HttpContext context,
IEnumerable<IConfigurationSection> sections, string prefix = "")
{
foreach (var section in sections)
{
await context.Response.WriteAsync($"{prefix}{section.Key}"); // : {envVar.Value}
await context.Response.WriteAsync($"\r\n");
if(section.GetChildren().Any())
{
await DumpAllConfigItems(context, section.GetChildren(), prefix + " ");
}
}
}

Resources