Azure Functions with imperative bindings

I recently needed to create an Azure Function, triggered off a storage queue, that checked the status of an external process via Http call. If that process was complete, download a JSON file and a png file to save them to Azure Blob Storage.

The problem I found was the standard Blob output binding only allows you to use a simple parameter that comes from an input binding as a variable in your output blob reference (i.e. blob filename). In their documentation example (shown below) they are using the {name} parameter from the original blob, in the BlobTrigger as the filename in the output bindings

[BlobTrigger("sample-images/{name}")] Stream image,
[Blob("sample-images-sm/{name}", FileAccess.Write)] Stream imageSmall,
[Blob("sample-images-md/{name}", FileAccess.Write)] Stream imageMedium)

This is ok if you want to save the new Blobs with the same name but in a different folder. It isn’t very useful when you want to create a new file name, based on a different variable. A variable such as a property on an object passed through a queue trigger (like below), or from a variable created within the function itself.

I wanted my filename based on the MonitorGuid property of the monitorTest object from the queue binding. The code below won’t run as it can’t determine the value of monitorTest.MonitorGuid when the function initiates.

public static async System.Threading.Tasks.Task RunAsync([QueueTrigger("monitor-test", Connection = "MyStorage")] MonitorTest monitorTest,
[Blob("results/{monitorTest.MonitorGuid}", FileAccess.Write)] Stream jsonResult,
[Blob("screenshots/{monitorTest.MonitorGuid}", FileAccess.Write)] Stream screenshotResult)
Interesting Tip

Just as an aside, Are you passing a JSON object as the message body of a queue binding? You can set the Type of your binding object (a MonitorTest object in this case) as a Strongly Typed object. The Azure Functions runtime will deserialize the incoming message into that object type.

Azure Functions imperative bindings to the rescue

The answer to our problem above is imperative binding. Imperative bindings are used when you need to configure the parameters of the binding at runtime, rather than at design time. The standard bindings we see above are declarative bindings, as they declare all the information required for the binding at design time.

Imperative bindings are defined by the use of the Binder, or IBinder parameter types. We would change the function definition to.

public static async System.Threading.Tasks.Task RunAsync([QueueTrigger("monitor-test", Connection = "MyStorage")] MonitorTest monitorTest,
IBinder jsonBinder,
IBinder screenshotBinder)

By declaring the outputs using IBinder, we can use the Bind<T> method on the binder to determine what type of binding we want. To save my JSON file and png file using the 2 IBinder declarations, I use the following declarations.

I’m binding the jsonBinder to a TextWriter and writing the JSON text returned to the Blob. I’m binding the screenshotBinder to a Stream and writing the binary response stream directly to a Blob.

response = await httpClient.GetAsync(new Uri(testURL));
            responseContentAsString = await response.Content.ReadAsStringAsync();
            if (response.IsSuccessStatusCode)
            {
                using (var myBlob = jsonBinder.Bind<TextWriter>(new BlobAttribute($"jsonresults/{monitorTest.Id}.json", FileAccess.Write)))
                {
                    await myBlob.WriteAsync(responseContentAsString);
                }
            }

            
            response = await httpClient.GetAsync(new Uri(screenshotUrl));
            if (response.IsSuccessStatusCode)
            {
                using (var myBlob = jsonBinder.Bind<Stream>(new BlobAttribute($"screenshots/{monitorTest.Id}.png", FileAccess.Write)))
                {
                    await myBlob.WriteAsync(await response.Content.ReadAsByteArrayAsync());
                }
            }

In summary, if you can’t setup your declarative bindings in just the way you need to, don’t despair, use an imperative binding and get the result you need.

If you haven’t tried Azure Functions yet, Microsoft offer a free trial of Windows Azure, including Azure Functions

Related Posts

Leave a Reply