Azure Web Apps Application Settings are a good way to manage configuration values. However managing them can be challenging.

Applications can use a lot of configuration settings, saving them manually on the Azure Portal is possible but it’s not a safe way:

  1. We have to format the key names
  2. We can easily forget some keys
  3. We can do copy / paste errors

Those « little » errors can have a big impact, for example forgetting to override a connection string can make the application to use a wrong database… that’s not good!

In this article we will explore how to use Azure Powershell to manage easily local ASP net core configuration files and application settings in Azure. More exactly, we will discover how to parse the project configuration files, format the keys and the values and push them directly to Azure Web App application settings with only few lines of Powershell.

 

The context

In ASP net core applications, application configurations are stored in a json file named appsettings.json. The idea is simple, this file is read by the application at runtime to retrieve data stored in it. To deal with multiple environments we can creates « override » files which permit to override specific values.

For my use case, I have a simple ASP net core API working with data coming from an Azure SQL Database and an Azure Storage Account. The API uses an authentication server and is used by a backend and a front end websites. This architecture is deployed on two environments, development (dev) and staging (stg).

Here is an example of the API the base appsettings.json file:

{
 "ConnectionStrings": {
  "BlobStorage": "DefaultEndpointsProtocol=https;AccountName=trastoragedev;AccountKey=*****************************TBUYFMycci8QtjZ0Kre0qgrD2R2BW20MocAUaWTZjUvg==;EndpointSuffix=core.windows.net",
  "Database": "Server=tcp:tra-sql-dev.database.windows.net,1433;Database=tra-db-dev;User ID=tra;Password=**********;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
  },
 "Endpoints": {
  "Auth": {
   "Url": "https://tra-auth-dev.azurewebsites.net"
   }
 },
 "Log": {
  "Url": "https://tra-logs.azurewebsites.net:82",
  "UserName": "tra",
  "Password": "tra!-net",
  "EnvTarget": "dev"
 }
}

This base configuration file points on dev environments (each connection string or remote resource contains the « dev » key word).

 

Here is an example of the appsettings.stg.json file, it overrides some values to point on the staging environment:

{
 "ConnectionStrings": {
  "BlobStorage": "DefaultEndpointsProtocol=https;AccountName=trastoragestg;AccountKey=*****************************TBUYFMycci8QtjZ0Kre0qgrD2R2BW20MocAUaWTZjUvg==;EndpointSuffix=core.windows.net",
  "Database": "Server=tcp:tra-sql-stg.database.windows.net,1433;Database=tra-db-stg;User ID=tra;Password=**********;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
  },
 "Endpoints": {
  "Auth": {
   "Url": "https://tra-auth-stg.azurewebsites.net"
  }
 },
 "Log": {
  "EnvTarget": "stg"
 }
}

 

In Azure, I have two Azure Web Apps to host and run my API code:

  • tra-api-dev
  • tra-api-stg

On both web apps, I have to set up application settings, for each setting stored in my project application files, I have to retrieve the key and the corresponding value. For the ConnexionStrings node in my appsettings.json file, following keys and values must be added:

  1. ConnectionStrings:BlobStorage / DefaultEndpointsProtocol=https;AccountName=trastoragedev;AccountKey=*****************************TBUYFMycci8QtjZ0Kre0qgrD2R2BW20MocAUaWTZjUvg==;EndpointSuffix=core.windows.net
  2. ConnectionStrings:Database / Server=tcp:tra-sql-dev.database.windows.net,1433;Database=tra-db-dev;User ID=tra;Password=**********;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;

 

I did it first manually using the Azure Portal, here is the result:

Screenshot of the tra-api-dev application settings (fig 1):

 

Screenshot of the tra-api-stg application settings (fig 2):

 

When we have few configurations in local configuration files, it « can » be done manually, however during development time, configuration files are used to grow and can finally contains a lot of keys and values. The copy / paste process on Azure Web app become a bad practice. Azure CLI and Azure Powershell can help us to automate the process.

 

Manage Web App application settings using Azure Powershell

The cmdlet Set-AzureRmWebApp allow us to update an Azure Web App, including applications settings.

To update the web app application settings it uses and object of type « Hashtable », the following script add two application settings to the azure web app tra-api-dev:


$subscriptionId = "******"
$rgName = "tra-rg"
$webAppName = "tra-api-dev"

Login-AzureRmAccount -SubscriptionId $subscriptionId

$appSettings = @{};
 
$appSettings.Add("ConnectionStrings:BlobStorage", "DefaultEndpointsProtocol=https;AccountName=trastoragedev;AccountKey=*****************************TBUYFMycci8QtjZ0Kre0qgrD2R2BW20MocAUaWTZjUvg==;EndpointSuffix=core.windows.net")
$appSettings.Add("ConnectionStrings:Database", "ConnectionStrings:Database / Server=tcp:tra-sql-dev.database.windows.net,1433;Database=tra-db-dev;User ID=tra;Password=**********;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;")

Set-AzureRmWebApp -ResourceGroupName $rgName -Name $webAppName -AppSettings $appSettings

This script works well! However the goal is not to write in the script all keys and values that we want to push as application settings. To solve this, we can write a powershell function which will parse our configuration files and dynamically populate the @appSettings Hashtable.

 

The parser function

In the .net world, we are used to play with json using json.net library. We can use it in C#, but we can use it in PowerShell too 🙂

The following function basically parses a json file. Moreover, it inspects each node to format the full node path as it’s waited in an Azure Web App: it concatenates each node level using « : » as separator:

[Reflection.Assembly]::LoadFile($newtonsofDllPath)

function Parse([string] $jsonPath){

 $appSettings = @{}

 $json = (Get-Content $jsonPath | Out-String)

 $jsonObject = [Newtonsoft.Json.Linq.JObject]::Parse($json)

 foreach($d in $jsonObject.Descendants()){

  if ($d.Children().HasValues -eq $false){
   $key = $d.Path.Replace(".", ":")
   $val = $d.Value.ToString()
   $appSettings.Add($key, $val)
  }
}

 return $appSettings
}

 

Using this function to parse our appsettings.json file, will populate an “Hastable” with the following content:

 

We can easily integrate it in our Powershell script:

#Azure configuration
$subscriptionId = "************************"
$rgName = "tra-rg"
$webAppName = "tra-api-stg"

#Asp net core configuration
$newtonsofDllPath = "C:\*******\Newtonsoft.Json.dll"

[Reflection.Assembly]::LoadFile($newtonsofDllPath) > $null

# Parse and format the specified json file
function Parse([string] $jsonPath){ 
 
$appSettings = @{}

$json = (Get-Content $jsonPath | Out-String)

$jsonObject = [Newtonsoft.Json.Linq.JObject]::Parse($json)

foreach($d in $jsonObject.Descendants()){ 
 
 if ($d.Children().HasValues -eq $false){
  $key = $d.Path.Replace(".", ":")
  $val = $d.Value.ToString()
  $appSettings.Add($key, $val)
 }

}

 return $appSettings

}

#Log to Azure Subscription
Login-AzureRmAccount -SubscriptionId $subscriptionId

$appSettings = Parse -jsonPath $jsonFileToParsePath

#Update web app application settings
Set-AzureRmWebApp -ResourceGroupName $rgName -Name $webAppName -AppSettings $appSettings

With this script we are able to parse our local configuration file and directly set the Azure Web App Applications Settings with no copy paste operations! In my example, I used an ASP Net core configuration file, but the Powershell script can be easily updated to manage other type of configuration files in XML or YAML.

 

Let’s go further

When I’m going on the Azure Portal to check Azure Web App application settings, I want to be sure that that each configuration defined in my local applications settings are present on the Azure Portal.

On the fig 2, we can see that there is only 4 applications settings defined whereas there are much more on the fig 1. That means that when the staging API will need to use the “Log:Password” value, it will check first in the Azure application settings if the key is defined, it won’t find it so it will use the value define in the appsetting.json file.

From my opinion, it’s a bit confusing because all settings used by the running application are not quickly visible in the Azure Portal. The solution consists of parsing both configuration files, the base one (applicationsettings.json) and the environnement target one (applicationsettigs.stg.json), and merge default and specific configurations.

Let’s create a “merge” function. It compares two “Hashtable” objects and returns a new one with defaults values and override values:

function Merge([hashtable] $appSettings, [hashtable] $appSettingsOverrides){

 $apps = @{}

 foreach($appSettingKey in $appSettings.Keys){

  $val = $appSettings[$appSettingKey];

  if ($appSettingsOverrides.ContainsKey($appSettingKey)){
   $val = $appSettingsOverrides[$appSettingKey]
  }

  $apps.Add($appSettingKey, $val)
 }

 return $apps
}

Now we can combine the « Parse » and the « Merge » functions:

function ParseAppSettings([string] $appSettingsFile, [string] $envTarget){

$appSettings = Parse -jsonPath $appSettingsFile

if (![string]::IsNullOrEmpty($envTarget)){
 $appSettingsOverrides = Parse -jsonPath $appSettingsFile.Replace("appsettings.json", "appsettings.$($envTarget).json")
 $appSettings = Merge -appSettings $appSettings -appSettingsOverrides $appSettingsOverrides
}

 return $appSettings
}

 

This last function parse two configuration files: The default one and the override one, then it merges both to retrieve all keys and with default or overrides values.

 

Here is the complete script using the « Merge » and « ParseAppSettings » functions:

#Azure configuration
$subscriptionId = "*************"
$rgName = "tra-rg"
$webAppName = "tra-api-stg"

#Asp net core configuration
$newtonsofDllPath = "C:\*******\Newtonsoft.Json.dll"
$jsonFileToParsePath = "C:\*******\appsettings.json"
$envTarget = "stg"

[Reflection.Assembly]::LoadFile($newtonsofDllPath) > $null

function Parse([string] $jsonPath){

 $appSettings = @{}

 $json = (Get-Content $jsonPath | Out-String)

 $jsonObject = [Newtonsoft.Json.Linq.JObject]::Parse($json)

 foreach($d in $jsonObject.Descendants()){

   if ($d.Children().HasValues -eq $false){
    $key = $d.Path.Replace(".", ":")
    $val = $d.Value.ToString()
    $appSettings.Add($key, $val)
   }
  }

 return $appSettings

}

function Merge([hashtable] $appSettings, [hashtable] $appSettingsOverrides){

 $apps = @{}

 foreach($appSettingKey in $appSettings.Keys){

  $val = $appSettings[$appSettingKey];

  if ($appSettingsOverrides.ContainsKey($appSettingKey)){
   $val = $appSettingsOverrides[$appSettingKey]
  }

   $apps.Add($appSettingKey, $val)
 }

 return $apps

}

function ParseAppSettings([string] $appSettingsFile, [string] $envTarget){

 $appSettings = Parse -jsonPath $appSettingsFile

 if (![string]::IsNullOrEmpty($envTarget)){
  $appSettingsOverrides = Parse -jsonPath $appSettingsFile.Replace("appsettings.json", "appsettings.$($envTarget).json")
  $appSettings = Merge -appSettings $appSettings -appSettingsOverrides $appSettingsOverrides
 }

 return $appSettings
}

Login-AzureRmAccount -SubscriptionId $subscriptionId

$appSettings = ParseAppSettings -appSettingsFile $jsonFileToParsePath -envTarget $envTarget

Set-AzureRmWebApp -ResourceGroupName $rgName -Name $webAppName -AppSettings $appSettings

Happy coding 🙂


//MS Build 2018 - Serverless announcements : Azure Function & Azure Event Grid Automate SQL Azure Backup using Azure Function and Azure PowerShell

Leave a Reply

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *