Azure Functions in Visual Studio Code

Visual Studio Code is more suitable for ARM development than Visual Studio. That makes it an excellent choice for Azure development when following the “Pipelines as Code” paradigm. There’s one exception to this rule and that is Azure Functions. Azure Functions involve C# code and you want to be able to edit, build and run functions on your local development machine.

This is actually easy. A full explanation can be found at the following Microsoft.com link: 

First install the Azure Functions extension.

Then open the folder where you want to create the function. Select the Azure Icon to see what functions you have already installed. From Azure screen: Create a new function.

You will have to choose a number of settings in the Create Function wizard, like the language, the type of trigger, the function name and namespace.

A first skeleton function appears like in Visual Studio. To build CTRL+SHIFT+B (like in Visual Studio). You can also access the build function via Terminal / Run Build Task.

In the Explorer you will see the following files are created:

Except for HTTP and timer triggers, bindings are implemented in extension packages. You must install the extension packages for the triggers and bindings that need them. In this case we build a Http trigger function, so we will skip this part.

To run and debug functions locally, press F5 (without debugging Ctrl+F5). You will be prompted to install Azure Functions Core Tools. Follow the link and download/install the Core Tools package. Core Tools includes the Azure Functions runtime (so download and installation might take some time).

After hitting F5 we see an address we can use to call the function locally. Set breakpoints and call the function from Postman. You will see the output of your function app in Postman. The streaming log is shown in the output window.

Note: If you include the ARM templates and the Azure Function Code in one directory, you need to open the specific folder with the function code in Visual Studio Code. Now you can run and debug the function. Possible you will have to add or adjust three files to the .vscode folder:

You will find the address to call from Postman on the Terminal tab down in your screen.

Azure Function Exponential Retry

What’s the default retry interval for a Http action in a logic app? How do I specify an exponential retry interval? I performed a little experiment in Azure to find the answers. 

First I created a Http triggered Azure function. The function gets the name node of the request body using data.name instead of data?name. Using data.name, I simply assume the name node is always present. Bad coding, but fit for my case. Next I created a logic app with a Http action calling the function. To enforce a Http 500 exception, I simply trigger the logic app without passing a message body. The Http 500 in turn, results in a retry which I’m after. Note that the retry interval must be specified in ISO8601 format, which is like: PTxxxH/M/S.

Here are the results:

Default retry:

This policy sends up to four retries at exponentially increasing intervals, which scale by 7.5 seconds but are capped between 5 and 45 seconds.

  • Run 09:04:17
  • Retry 09:04:17
  • Retry 09:04:23
  • Retry 09:04:34
  • Retry 09:05:01

Retry 5 times, interval=5S, min interval=5S, max interval=2M

  • Run 09:11:15
  • Retry 09:11:15
  • Retry 09:11:20
  • Retry 09:11:31
  • Retry 09:11:45
  • Retry 09:12:14

Retry 8 times, interval 10S, min interval 5S, max interval 2M

  • Run 09:16:35
  • Retry 09:16:35
  • Retry 09:16:40
  • Retry 09:16:59
  • Retry 09:17:25
  • Retry 09:18:19
  • Retry 09:20:19
  • Retry 09:22:19
  • Retry 09:24:19

Retry 5 times, interval=1M, min interval=30S, max interval=3M

  • Run 09:27:49
  • Retry 09:27:49
  • Retry 09:28:22
  • Retry 09:29:43
  • Retry 09:32:44
  • Retry 09:35:44

Resume:

  • The first retry occurs immediately (both for exponential retry interval and fixed retry interval).
  • The min interval must be between 00:00:05 and the interval specified.
  • The second retry occurs between min interval and interval. It can be equal to min interval, but that doesn’t have to be the case.
  • The max interval is the max retry interval. The retries don’t have to be capped before the max interval (as the default interval states).

Return Xml from Azure Function

To return XML from a function, you can use ContentResult instead of ActionResult. Relevant statement: return new ContentResult { Content = retval.InnerXml, ContentType = “application/xml” };

using System.Threading.Tasks;
using System.Xml;

namespace Function.RelatieHelper
{
    public static class VictimMessage
    {
        [FunctionName("CreateVictimSiblingMessage")]
        public static async Task<IActionResult> CreateVictimSiblingMessage(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string msg = await new StreamReader(req.Body).ReadToEndAsync();
            string victimId = GetHeaderValue(req, "victimId");


            if (msg == null)
            {
                return (ActionResult)new BadRequestObjectResult("XmlMessage ism null");
            }

            if (String.IsNullOrEmpty(victimId))
            {
                return (ActionResult)new BadRequestObjectResult("VictimId is null");
            }

            XmlDocument retval = new XmlDocument();

            using (var stream = new MemoryStream())
            {
                var writer = new StreamWriter(stream);
                writer.Write(msg);
                writer.Flush();
                stream.Position = 0;

                retval.Load(stream);

                retval.SelectSingleNode("//*[local-name() = 'relationid']").InnerText = victimId;
                retval.SelectSingleNode("//*[local-name() = 'relation_extern_id']").InnerText = victimId;
                
            }

            return new ContentResult { Content = retval.InnerXml, ContentType = "application/xml" };
                            
        }

        private static string GetHeaderValue(HttpRequest req, string headerName)
        {

            StringValues headerValues;
            string headerValue = null;

            if (req.Headers.TryGetValue(headerName, out headerValues))
            {
                headerValue = headerValues[0];
            }

            return headerValue;

        }
    }
}

Delete read-only Functions

I’m developing functions in Visual Studio instead of developing them in the Azure Portal. If you open the function in the Azure Portal you will receive the following message:
Your app is currently in read-only mode because you have published a generated function.json. Changes made to function.json will not be honored by the Functions runtime.

One of the consequences of this message is, that it seems like you can’t remove the function anymore. Very annoying, but … there’s a workaround. When you open the function container under menu option Function Apps, you will see two tabs at the top: Overview and Platform Settings. Go to Platform Settings and select the App Service Editor. You will see all functions displayed. Right-click the function you want to delete and select Delete.

Apparently a simple fix of this issue will go out soon.

Timer trigger to read messages from a queue

When you want to develop an Azure Function to process messages from a queue, the first hunch is to use a queue trigger. But, let’s say you want to processes messages from a queue every minute. In that case you need a timer trigger. In the example below you see an example of an Azure Function with a timer trigger, an import of multiple libraries and a call of a Logic App using an URI from AppSettings. I need to check if I found a message (message!=null). On the other hand, if there are multiple messages on the queue, they will be processed one-by-one.

[box type=”info”]

#r “Microsoft.WindowsAzure.Storage”
#r “Newtonsoft.Json”

using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue;
using System.Text;
using System.Net.Http;
using Newtonsoft.Json;

private static string logicAppUri = Environment.GetEnvironmentVariable(“ProcessOpdrachtenURI”);

public static async Task Run(TimerInfo myTimer, TraceWriter log)
{

log.Info($”Function TriggerTimedProcessOpdrachten started”);

// Retrieve storage account from connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable(“ahakstorage_STORAGE”));

// Create the queue client.
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();

// Retrieve a reference to a queue.
CloudQueue opdrachtQueue = queueClient.GetQueueReference(“opdrachtevents”);

// GetMessage
CloudQueueMessage message = await opdrachtQueue.GetMessageAsync();
if (message != null)
{

log.Info($”{message.AsString}”);

using (var client = new HttpClient())
{
var response = client.PostAsync(logicAppUri, new StringContent(message.AsString, Encoding.UTF8, “application/json”)).Result;
}

opdrachtQueue.DeleteMessage(message);

}

log.Info($”Function TriggerTimedProcessOpdrachten ended”);

}

[/box]

Note: In the end I didn’t need the Newtonsoft.Json library, but I left that library in for learning purposes.

Asynchronuous Processing

Asynchronuous processing is a feature you might not use that often. Below link explains the concept: Link: Asynchronuous Programming.

I needed the asynchronous call in an Azure function. More specifically I had to trigger a logic app for four different regions. I found however that if one of the regions failed, the processing of the other regions was skipped. That’s when I thought of processing the logic apps asynchronuously.

First I changed the signature of the Azure function to an asynchronuous method:

public static async Task Run(TimerInfo schedule, TraceWriter log)
instead of: public static void Run(TimerInfo schedule, TraceWriter log).

Next I changed the call to the logic app:
await Task.Run(() => client.PostAsync(logicAppUri, new StringContent(content, Encoding.UTF8, “application/json”)));
instead of: var response = client.PostAsync(logicAppUri, new StringContent(content, Encoding.UTF8, “application/json”)).Result;

Trace an async program

Unforunately, this option didn’t work. For that reason I turned to another option using Parallel.ForEach, instead of the regular ForEach. See: Link MSDN. The function is implemented as follows:

using System;
using System.Threading.Tasks;Link MSDN
using System.Net.Http;
using System.Text;

private static string logicAppUri = Environment.GetEnvironmentVariable(“ProcessAGAEventsURI”);
private static string content;

public static void Run(TimerInfo schedule, TraceWriter log)
{

string[] regions = new string[4];
regions[0]=”North”;
regions[1]=”South”;
regions[2]=”East”;
regions[3]=”West”;

Parallel.ForEach(regions, (regionCode) =>
//foreach (string regionCode in regions)
{
log.Info($”regionCode: {regionCode}”);
using (var client = new HttpClient())
{
var response = client.PostAsync(logicAppUri, new StringContent(content, Encoding.UTF8, “application/json”)).Result;
}
});
}

Deploy Azure Functions using Powershell and FTP

Deploying an Azure Function to Azure to a large extent resembles deploying an API App. See Link:  Deploy Api App. The only problem is, we can’t create a deployment package for Azure Functions via Visual Studio. Instead, go to the Azure Portal. Open the relevant Function container and click Download App Content.

You will now get a zip file with subfolders per Azure Function. Each Azure Function folder contains a csx file with the Azure Function code and a json file for configuration settings. Also note we can define parameters at the Function Container level. An example is a queue trigger function where the address from the Logic App is taken from the settings. Below you will see the ps1 file and an example psx file.

DeployTriggerFunctions.ps1:
param (
[parameter(Mandatory = $true)][string] $paramFileName
)

Import-Module AzureRM.Resources

Write-Host “Login to Azure” –fore gray;
Login-AzureRmAccount

# $PSScriptRoot is null when you run from Powershell ISE
# Run from cmd file
$baseDir = $PSScriptRoot
$pos = $basedir.LastIndexOf(‘\’)
$baseDirParam = $baseDir.Substring(0,$pos)
$dirParam = $baseDirParam + “\AAA-DeployResources\” + $paramFileName
$dirUtils = $baseDirParam + “\AAA-DeployResources\Utils.psm1”
Write-Host “BaseDir: ” $baseDir –fore gray;
Write-Host “Dir ParameterFile: ” $dirParam –fore gray;
Write-Host “Dir UtilsFile: ” $dirUtils –fore gray;

#region Load Parameter file
. $dirParam
#endregion

#region Import Utils file
Import-Module “$dirUtils”
#endregion

Write-Host “Functions Container: ” $nameFunctionsContainer –fore gray
$result=’false’
$result = UploadToFTP -webAppName $nameFunctionsContainer -rg $resourcegroupFunctions -sourceDir $sourceDirTriggerFunctions

if ($result=’true’)
{
Write-Host $(Get-Date).ToString(“yyyyMMdd_HHmss”) ” Add Azure Function Container ” $nameFunctionsContainer ” to Azure succeeded” –fore green
}
else
{
Write-Host $(Get-Date).ToString(“yyyyMMdd_HHmss”) ” Add Azure Function Container ” $nameFunctionsContainer ” to Azure failed” –fore red
}

# Add ApplicationSettings
$appSettingsTriggerFunctions = @{“AzureWebJobsDashboard”=$azureWebJobsDashboard;”AzureWebJobsStorage”=$azureWebJobsStorage;”FUNCTIONS_EXTENSION_VERSION”=$FUNCTIONS_EXTENSION_VERSION; `
“WEBSITE_NODE_DEFAULT_VERSION”=$WEBSITE_NODE_DEFAULT_VERSION_FUNC;”ahakstorage_STORAGE”=$storageConnectionString;”ProcessAGAEventsURI”=$ProcessAGAEventsURI; `
“ProcessAGABeoordelingURI”=$ProcessAGABeoordelingURI;”ProcessAGPURI”=$ProcessAGPURI;”ProcessAGPBeoordelingURI”=$ProcessAGPBeoordelingURI;”ProcessAnnuleringenURI”=$ProcessAnnuleringenURI; `
“ProcessAnnuleringGereedURI”=$ProcessAnnuleringGereedURI;”ProcessBijstellingURI”=$ProcessBijstellingURI;”ProcessOpdrachtenURI”=$ProcessOpdrachtenURI;”ProcessOpdrachtInfoURI”=$ProcessOpdrachtInfoURI; `
“ProcessPlanningURI”=$ProcessPlanningURI;”ProcessStatusUpdateURI”=$ProcessStatusUpdateURI;”ProcessTGEventsURI”=$ProcessTGEventsURI;”ProcessWorkOrderURI”=$ProcessWorkOrderURI}
Set-AzureRmWebApp -Name $nameFunctionsContainer -AppSettings $appSettingsTriggerFunctions -ResourceGroupName $resourcegroupFunctions

run.cxs:
using System;
using System.Threading.Tasks;
using System.Net.Http;
using System.Text;

private static string logicAppUri = Environment.GetEnvironmentVariable(“ProcessAnnuleringenURI”);

public static void Run(string myQueueItem, TraceWriter log)
{
log.Info($”C# Queue trigger function processed: {myQueueItem}”);
using (var client = new HttpClient())
{
var response = client.PostAsync(logicAppUri, new StringContent(myQueueItem, Encoding.UTF8, “application/json”)).Result;
}
}

Again we upload the files using FTP. We can use the same FTP script we saw when deploying the API App. There’s one gotcha. If we deploy the Azure Function this way and we go the Integrate tab, we will note that the storage account connection name (in case of a queue trigger) is not selected. We have to select this setting manually.

Latest gotchas for Azure Functions

Delete a function:
You can remove a function from a function app via the Azure Portal. Select the function in the Function App. Select the Manage menu en click [Delete]. Very easy. I just didn’t know it was possible.

Timer trigger:
If you want to build an Azure Function that is executed let’s say every five minutes, you can use the Azure Function template TimerTrigger – C#. To specify the interval, you need to use cron expressions. An explanation on cron expressions can be found: here. Examples of cron expressions are also given on the Azure Portal page where you specify the trigger for the function.

Call a Logic App from an Azure function
Use the HttpClient object to send a post request. An example is shown below:

using System;
using System.Threading.Tasks;
using System.Net.Http;
using System.Text;

private static string logicAppUri = @”https://prod-03.westeurope.logic.azure.com:443/workflows/…&#8221;;
private static string content;

public static void Run(TimerInfo schedule, TraceWriter log)
{
DateTime endDate = System.DateTime.Now;
TimeSpan fiveMinutes = new TimeSpan(0, 0, 5, 0);
DateTime startDate = endDate – fiveMinutes;
string formattedEndDate = endDate.ToString(“yyyy-MM-ddTHH:mm:00.000Z”);
string formattedStartDate = startDate.ToString(“yyyy-MM-ddTHH:mm:00.000Z”);
string regionCode = Environment.GetEnvironmentVariable(“RegionCode”);

using (var client = new HttpClient())
{
content = $”{{‘regioncode’: ‘{regionCode}’, ‘from’ : ‘{formattedStartDate}’, ‘to’ : ‘{formattedEndDate}’}}”;
var response = client.PostAsync(logicAppUri, new StringContent(content, Encoding.UTF8, “application/json”)).Result;
}
}

One addition: Azure works with UTC datetime out-of-the-box. Often you want to use local datetime however. This is the code I added at the start of the function:
var cetZone = TimeZoneInfo.FindSystemTimeZoneById(“Central Europe Standard Time”);
DateTime endDate = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, cetZone);

Use configuration settings
An example can already be taken from the above example: string regionCode = Environment.GetEnvironmentVariable(“RegionCode”). To specify the application settings, click [Function app settings] in the bottom of the menu. Click either [Configure App Settings] or [Go to App Service Settings] and you can specify your application settings like RegionCode.

 

Example Logic App with Azure Function

It’s basic, but I am not that familiar with JSON yet. I saw an example where you use a logic app to retrieve all tweets with anchor #LogicApps. Next you see an Azure function named Returning ComplexResp is called. See the screenprint below:

TweetLogicApp

What we see, is that the Azure function takes input from the Twitter functoid. Json property “text” is set to Tweet text, “by” is set to TweetedBy. Now. To give a better idea. What does the code of ReturningComplexResp look like? Actually it returns in the Body a value for Msg (being a combination of the input parameters TweetedBy and TweetText) and a value FileName.

FunctionComplexCode

When we run the Azure function we see we get the following response:

FunctionComplexOutput

Now, as the last step, we create an API to add a file to the root folder of a DropBox directory. First we create a DropBox API with the Body of the ReturningComplexResp Azure function as both the filename and the content.

DropBoxFile

Then we switch to code view and see how we can actually set the content to Body.Msg and filename to Body.FileName as returned by the Azure function:

DropBoxCodeView

This is the actual JSON I wanted to show. Simple, but powerful. When we switch back to Design View again, we see the File Name and File Content parameters have been changed. This shows that Code View and Designer View are actually aligned. Nice! I’ve had another experience before.

DropBoxFileAdjusted