How to use Application settings instead of screts.json when deployed to Azure - azure

I use the secrets.json file to store credentials for my Blazor .Net Core 3.0 Web App and use IConfiguration to get the values like this
public Startup(IConfiguration configuration)
{
Configuration = configuration;
var username = Configuration["Account:Username"];
var password = Configuration["Account:Password"];
new Account(username, password);
}
public IConfiguration Configuration { get; }
Which works fine on my machine in Development, but when I switch to Release in my app Properties > Debug > Environment variables > ASPNETCORE_ENVIRONMENT, the secrets.json doesn't get loaded. Same when I upload the app to Azure, where I have set up the variables as Application settings, it doesn't work. What am I missing here? How can I set up the Web App in Production on Azure, so that I can use the credentials?
edit:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});

Related

How to retrieve Azure App Configuration Service settings while in Program.cs

I am using Asp.NET Core 5 (v5.0.6) web API deployed as an Azure App Service and I am migrating from using appsettings.json in the API to integrating with Azure App Configuration service. I have the Azure App Configuration Service set up but the issue I am having is how to access values in my Azure App Configuration service for retrieving a connection string for database access while I am still in Program.cs.
Note that in the CreateHostBuilder() method I am examining the context.HostingEnvironment.IsDevelopment() environment variable and if it is "IsDevelopment", implying a local DEV, I am reading an Azure App Configuration Service connection string via User Secrets but if it is not a local DEV, than I rely on the Managed Identity and just pass in the endpoint value from appsettings.json.
The only values I want to get that are not in Azure App Configuration Service is the local DEV Azure App Configuration Service Connection string (from User Secrets) and the Azure App Configuration Service endpoint from Appsettings.json. All other settings should come from Azure App Configuration Service.
The problem I am trying to solve is how to access the values in Azure App Configuration Service, while still in Program.cs, to retrieve the connection string for access to the Azure SQL database I am using for logging.
In the code below, when I link the Azure App Configuration in the CreateHostBuilderMethod and call build, I expected the values in Azure App Configuration Service to then be available via the static Configuration property. However when I try to retrieve the connection string value, it is always null.
How can I correctly retrieve the values for properties in Azure App Configuration Service to use them in Program.cs?
Here is my Program.cs file;
public class Program
{
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
.AddUserSecrets<Startup>()
.Build();
public static void Main(string[] args)
{
var host = CreateHostBuilder(args)
.Build();
var connectionString = Configuration["CoreApi:Settings:LoggingDb"]; //<-- This returns null
const string tableName = "ApiLogs";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Filter.ByExcluding(Matching.FromSource("Microsoft.EntityFrameworkCore.Query"))
.WriteTo.MSSqlServer(
connectionString: connectionString,
sinkOptions: new MSSqlServerSinkOptions { TableName = tableName })
.CreateLogger();
// TODO Enable to debug any startup Serilog issues. Make sure to comment out for PROD
// Serilog.Debugging.SelfLog.Enable(msg =>
// {
// Debug.Print(msg);
// Debugger.Break();
// });
//var host = CreateHostBuilder(args)
// .Build();
try
{
Log.Information("Starting ConfirmDelivery API");
host.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "ConfirmDelivery API Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var settings = config.Build();
if (context.HostingEnvironment.IsDevelopment())
{
var connectionString = settings.GetConnectionString("AzureAppConfiguration");
config.AddAzureAppConfiguration(connectionString);
}
else
{
var endpoint = settings["AppConfigEndpoint"];
var credentials = new ManagedIdentityCredential();
config.AddAzureAppConfiguration(options =>
{
options.Connect(new Uri(endpoint), credentials);
});
}
}).ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Try the code below to get a config value:
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
namespace myapp
{
class Program
{
static void Main(string[] args)
{
var configItemName = "";
var appConfigConnectionString = "";
var builder = new ConfigurationBuilder();
builder.AddAzureAppConfiguration(appConfigConnectionString);
var config = builder.Build();
Console.WriteLine(config[configItemName]);
}
}
}
Result :

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?

Issue with .netcore 2.1 logging to Azure Blobs

I have a .netcore 2.1 api. I am trying to do some logging in Azure Blobs.
I have been using Microsoft.Extensions.Logging.AzureAppServices(version 2.2.0) in .netcore 2.2 api's with no issue and it logs information to the Azure blobs beautifully.
But when I try the same code with Microsoft.Extensions.Logging.AzureAppServices (version 2.1.1) in .netcore 2.1 it fails to log the information (even though I get the desired out from the API endpoint).
Code that I have tried,in program.cs
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Main() method.");
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.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";
}))
.UseStartup<Startup>();
}
And in azure I have added the settings as in the image below.
Now to test it I had added some logging in the starup.cs and program.cs and controller.cs.
Logs from Startup.cs is being printed into the output blobs, but the other cs files are unable to log the information into the blob.
Anybody has any idea what I am doing wrong?
To configure provider settings, use AzureFileLoggerOptions and AzureBlobLoggerOptions, as shown in the following example:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.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";
}))
.UseStartup<Startup>();
or Alternatively like below:
public void ConfigureServices(IServiceCollection services)
{
//...
services.Configure<AzureFileLoggerOptions>(Configuration.GetSection("AzureLogging"));
}
Appsettings.json
"AzureLogging": {
"FileName" : "azure-diagnostics-",
"FileSizeLimit": 50024,
"RetainedFileCountLimit": 5
}
When you deploy to an App Service app, the application honors the settings in the App Service logs section of the App Service page of the Azure portal. When the following settings are updated, the changes take effect immediately without requiring a restart or redeployment of the app.
Application Logging (Filesystem)
Application Logging (Blob)
Please ensure to have setting like above.Hope it helps.

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

ASP.Net Core 2.1 Serilog SQL Server Sink using Azure SQL is working locally but not from Azure app service

I have an ASP.Net Core 2.1 Website that uses Azure SQL Database for the Microsoft Identity component.
I added a Logs table to that database and added Serilog to my website with the SQL Server Sink.
When I run the website locally, while still connected to the Azure SQL database, I can see my log entries in the Logs table just fine. However, when I deploy the website to my Azure App Service, I no longer get any log entries in the Logs table of the database.
Mind you, in the deployed version, I am connecting to and using the Azure SQL database for my MS Identity stuff just fine and I can create new users and edit existing users just fine. So I know the Connection String in my App Service Application Settings is correct.
I have reviewed the Serilog MSSQL Github to compare their configuration recommendations to my own and could not find anything that stood out.
I have this setup working correctly on an ASP.Net Core API that I deploy to another Azure App Service. That service uses a different database but it is on the same SQL Server resource.
I have reviewed the list of SO posts recommended when I started this question with no luck.
I ran the following SQL on the database when I first set up the user account;
EXEC sp_addrolemember N'db_datareader', N'myuser'
EXEC sp_addrolemember N'db_datawriter', N'myuser'
EXEC sp_addrolemember N'db_ddladmin', N'myuser'
And, as I stated, the user account can update and add user data in the AspNetUsers table just fine. So, it doesn't seem like a user account issue.
I have verified that the connection string in my Azure app service DEV deployment slot (the one I am testing), Application Settings, Connection Strings is the exact same as what I have in my local DEV UserSecrets. Plus, again, I can read/write to the AspNet* tables in that same database when deployed to Azure.
Here is my Program.cs class where I set up Serilog;
public class Program
{
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
.AddUserSecrets<Startup>()
.AddEnvironmentVariables()
.Build();
public static void Main(string[] args)
{
var connectionString = Configuration.GetConnectionString("MyConnectionString");
const string tableName = "Logs";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
.WriteTo.MSSqlServer(connectionString, tableName)
.CreateLogger();
// TODO Enable to debug any startup Serilog issues. Make sure to comment out for PROD
//Serilog.Debugging.SelfLog.Enable(msg =>
//{
// Debug.Print(msg);
// Debugger.Break();
//});
try
{
Log.Information("Starting Application");
CreateWebHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog();
}
}
The only difference between the API that I have deployed in Azure that is writing logs to Azure SQL and this website is that in the API, which is older, I have
public static IWebHost BuildWebHost(string[] args)
in program.cs whereas the newer website has
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
So... any ideas would be greatly appreciated.
[UPDATE 1/23/19]
I added the connection string directly to the
var connectionString
in Program.cs rather than getting it from
Configuration.GetConnectionString("MyConnectionString")
and it started logging to the database.
So it seems the issue is with Program.cs being able to read the connection string from Azure App Service deployment slot, Application Settings, Connection Strings section.
This connection string is being correctly read from Startup.cs and has worked since I first created the website.
So, is there some known issue with Azure not being able to read values from deployment slot Application Settings / Connection Strings from Program.cs?
Since there seems to be an issue with Azure, that it doesn't provide the application settings to the web app until CreateWebHostBuilder is invoked, a straightforward workaround (assuming that hardcoding the connection string in the source code isn't a viable option) would be to configure Serilog to use SqlServer in Startup.cs instead of Program.cs.
If it's important to log the initial events that occur during the app's starting up, Serilog can temporarily be configured in Program.cs to write to a file.
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var AzureLogFilePath = #"D:\home\LogFiles\Application\log.txt";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.File(path: AzureLogFilePath, fileSizeLimitBytes: 1_000_000, rollOnFileSizeLimit: true, shared: true);
.CreateLogger();
try
{
Log.Information("Starting Application");
CreateWebHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog();
}
Startup.cs:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName ?? "Production"}.json", optional: true)
.AddEnvironmentVariables();
if (env.IsDevelopment()) builder.AddUserSecrets<Startup>(); // according to the docs, AddUserSecrets should be used only in development
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration.GetConnectionString("MyConnectionString");
const string tableName = "Logs";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
.WriteTo.MSSqlServer(connectionString, tableName)
.CreateLogger();
//...
}
}

Resources