cowinr
7/27/2019 - 3:55 PM

Azure Functions

Key Configuration Files

Local Settings

The file local.settings.json stores app settings, connection strings, and settings used by local development tools. These should be the same as the application settings in the function app in Azure. If you have created application settings in Azure, you can download them into your local settings file.

Because it contains secrets it never gets published, and is excluded from source control.

Host

The host.json metadata file contains global configuration options that affect all functions for a function app.

{
    "version": "2.0"
}

These settings apply both when running locally and in Azure.

See here for more. Can include settings for extensions, health monitoring and logging for example.

Bindings - function.json

In a C# class library project, the bindings are defined as binding attributes on the function method. The function.json file is then autogenerated based on these attributes:

{
  "generatedBy": "Microsoft.NET.Sdk.Functions-1.0.29",
  "configurationSource": "attributes",
  "bindings": [
    {
      "type": "httpTrigger",
      "methods": ["get", "post"],
      "authLevel": "function",
      "name": "req"
    }
  ],
  "disabled": false,
  "scriptFile": "../bin/functions.dll",
  "entryPoint": "My.Functions.HttpTriggerCSharp.Run"
}
// HTTP trigger, with Storage Queue o/p
[FunctionName("HttpTriggerQueueOutput")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    [Queue("outqueue"), StorageAccount("AzureWebJobsStorage")] ICollector<string> msg, ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");
    
    // Add a message to the output collection.
    msg.Add(string.Format("Name passed to the function: {0}", name));
}

// Queue trigger, Table o/p
[FunctionName("QueueTriggerTableOutput")]
[return: Table("outTable", Connection = "MY_TABLE_STORAGE_ACCT_APP_SETTING")]
public static Person Run(
    [QueueTrigger("myqueue-items", Connection = "MY_STORAGE_ACCT_APP_SETTING")] JObject order,
    ILogger log)
{
    return new Person() {
            PartitionKey = "Orders",
            RowKey = Guid.NewGuid().ToString(),
            Name = order["Name"].ToString(),
            MobileNumber = order["MobileNumber"].ToString()
    };
}

// Blob trigger
[FunctionName("BlobTrigger")]
public static void Run([BlobTrigger("samples-workitems/{name}")] Stream myBlob, string name, ILogger log)
{
    log.LogInformation($"C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes");
}

// CosmosDB trigger
[FunctionName("CosmosTrigger")]
public static void Run(
  [CosmosDBTrigger(
    databaseName: "ToDoItems",
    collectionName: "Items",
    ConnectionStringSetting = "CosmosDBConnection",
    LeaseCollectionName = "leases",
    CreateLeaseCollectionIfNotExists = true)
  ] IReadOnlyList<Document> documents,
    ILogger log)
{
    if (documents != null && documents.Count > 0)
    {
        log.LogInformation($"Documents modified: {documents.Count}");
        log.LogInformation($"First document Id: {documents[0].Id}");
    }
}

Chaining

Fan out / fan in

Async HTTP APIs

See Create your first durable function in C#.

Monitor

Human interaction

Aggregator (preview)

// Durable function chaining
public static async Task<object> Run(DurableOrchestrationContext context)
{
    try
    {
        var x = await context.CallActivityAsync<object>("F1");
        var y = await context.CallActivityAsync<object>("F2", x);
        var z = await context.CallActivityAsync<object>("F3", y);
        return  await context.CallActivityAsync<object>("F4", z);
    }
    catch (Exception)
    {
        // Error handling or compensation goes here.
    }
}

// Fan out / fan in
public static async Task Run(DurableOrchestrationContext context)
{
    var parallelTasks = new List<Task<int>>();

    // Get a list of N work items to process in parallel.
    object[] workBatch = await context.CallActivityAsync<object[]>("F1");
    for (int i = 0; i < workBatch.Length; i++)
    {
        Task<int> task = context.CallActivityAsync<int>("F2", workBatch[i]);
        parallelTasks.Add(task);
    }

    await Task.WhenAll(parallelTasks);

    // Aggregate all N outputs and send the result to F3.
    int sum = parallelTasks.Sum(t => t.Result);
    await context.CallActivityAsync("F3", sum);
}

// Monitor - Not much of a clue what is going on here

// Human
public static async Task Run(DurableOrchestrationContext context)
{
    await context.CallActivityAsync("RequestApproval");
    using (var timeoutCts = new CancellationTokenSource())
    {
        DateTime dueTime = context.CurrentUtcDateTime.AddHours(72);
        
        // create the durable timer
        Task durableTimeout = context.CreateTimer(dueTime, timeoutCts.Token);

        // wait for approval
        Task<bool> approvalEvent = context.WaitForExternalEvent<bool>("ApprovalEvent");
        
        // check approval
        if (approvalEvent == await Task.WhenAny(approvalEvent, durableTimeout))
        {
            timeoutCts.Cancel();
            await context.CallActivityAsync("ProcessApproval", approvalEvent.Result);
        }
        else
        {
            await context.CallActivityAsync("Escalate");
        }
    }
}

// Aggregator
// Using a Durable Entity function, one can implement this pattern easily as a single function.
[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    int currentValue = ctx.GetState<int>();

    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            int amount = ctx.GetInput<int>();
            currentValue += operand;
            break;
        case "reset":
            currentValue = 0;
            break;
        case "get":
            ctx.Return(currentValue);
            break;
    }

    ctx.SetState(currentValue);
}
// yeah, not really sure what's going on here