Understanding Azure Durable Functions - Part 6: Activity Functions with Additional Input Bindings

This is the sixth part in a series of articles.

Up until this point in this series, the activity function has received it’s data from the calling orchestration function, for example passing in a Greeting instance from the orchestrator:

public class Greeting
{
    public string CityName { get; set; }
    public string Message { get; set; }
}

[FunctionName("DataExample2_ActivityFunction")]
public static string SayHello([ActivityTrigger] Greeting greeting, ILogger log)
{            
    log.LogInformation($"Saying '{greeting.Message}' to {greeting.CityName}.");
    return $"{greeting.Message} {greeting.CityName}";
}

Because activity functions are just like regular Azure functions, we can also add input bindings:

[FunctionName("DataExample3_ActivityFunction")]
public static string SayHello(
    [ActivityTrigger] Greeting greeting, 
    [Blob("cities/{data.CityId}.txt")] string city,
    ILogger log)
{            
    log.LogInformation($"Saying '{greeting.Message}' to {city}.");
    return $"{greeting.Message} {city}";
}

In the preceding code, in addition to the activity trigger, the activity function also makes use of a blob storage input binding. When this activity function executes, the contents of a blob will be read in and the text contained therein used for the city name. Notice the binding syntax  [Blob("cities/{data.CityId}.txt")] string city, this will look for a property on the incoming data called CityId. The blob container that will be read from is fixed and is called cities. This works because the Greeting class has been modified as follows:

public class Greeting
{
    public string CityId { get; set; }
    public string Message { get; set; }
}

I just wanted to interrupt  this blog post with a quick reminder that you can start watching my Pluralsight courses with a free trial.

The following is a complete listing of these functions:

using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;

namespace DurableDemos
{
    public static class DataExample3
    {
        public class Greeting
        {
            public string CityId { get; set; }
            public string Message { get; set; }
        }

        public class GreetingsRequest
        {
            public List<Greeting> Greetings { get; set; }
        }

        [FunctionName("DataExample3_HttpStart")]
        public static async Task<HttpResponseMessage> HttpStart(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequestMessage req,
            [OrchestrationClient]DurableOrchestrationClient starter,
            ILogger log)
        {
            var data = await req.Content.ReadAsAsync<GreetingsRequest>();

            string instanceId = await starter.StartNewAsync("DataExample3", data);

            log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

            return starter.CreateCheckStatusResponse(req, instanceId);
        }

        [FunctionName("DataExample3")]
        public static async Task RunOrchestrator(
            [OrchestrationTrigger] DurableOrchestrationContext context, ILogger log)
        {
            log.LogInformation($"************** RunOrchestrator method executing ********************");

            GreetingsRequest data = context.GetInput<GreetingsRequest>();

            foreach (var greeting in data.Greetings)
            {
                await context.CallActivityAsync<string>("DataExample3_ActivityFunction", greeting);
            }            
        }

        [FunctionName("DataExample3_ActivityFunction")]
        public static string SayHello(
            [ActivityTrigger] Greeting greeting, 
            [Blob("cities/{data.CityId}.txt")] string city,
            ILogger log)
        {            
            log.LogInformation($"Saying '{greeting.Message}' to {city}.");
            return $"{greeting.Message} {city}";
        }
    }
}

Now the following JSON can be posted to the client function (http://localhost:7071/api/DataExample3_HttpStart on my local dev environment):

{
    "Greetings": [{
            "CityId": "42",
            "Message": "Yo!"
        },
        {
            "CityId": "100",
            "Message": "Good day"
        }
    ]
}

In blob storage there are a couple of blobs with names 42, and 100, these match the IDs being passed in the preceding JSON.

Blobs in blob storage

Now when the activity function executes it will read in the corresponding blob which contains the city name as the following (simplified) output shows:

Executing 'DataExample3_HttpStart' (Reason='This function was programmatically called via the host APIs.', Id=b76cb2a3-ef54-48d4-bcd2-5854aedebe62)
Started orchestration with ID = '08e79880b6e844f5a3b2acbe5ba46244'.
Executed 'DataExample3_HttpStart' (Succeeded, Id=b76cb2a3-ef54-48d4-bcd2-5854aedebe62)
Executing 'DataExample3_ActivityFunction' (Reason='', Id=8f965987-d893-4755-b342-30c84c682e70)
Saying 'Yo!' to New York.
Executed 'DataExample3_ActivityFunction' (Succeeded, Id=8f965987-d893-4755-b342-30c84c682e70)
Executing 'DataExample3_ActivityFunction' (Reason='', Id=7b0aaee2-0132-40bd-90df-a8b741db6491)
Saying 'Good day' to London.
Executed 'DataExample3_ActivityFunction' (Succeeded, Id=7b0aaee2-0132-40bd-90df-a8b741db6491)

Notice the messages “Saying 'Yo!' to New York.” and “Saying 'Good day' to London.” – New York and London have been read from blob storage.

You could also use output bindings in the activity function, such as writing to queue storage:

[FunctionName("DataExample3_ActivityFunction")]
public static string SayHello(
    [ActivityTrigger] Greeting greeting, 
    [Blob("cities/{data.CityId}.txt")] string city,
    [Queue("greetings")] out string queueMessage,
    ILogger log)
{            
    log.LogInformation($"Saying '{greeting.Message}' to {city}.");

    var message = $"{greeting.Message} {city}";

    queueMessage = message;

    return message;
}

Now in addition to returning the message to the orchestration, the activity also writes a message to the queue.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Add comment

Loading