Writing Queue Messages with Azure Function Output Bindings

Posted by

The art of simplicity is a puzzle of complexity, or so the saying goes. A few days ago, I was struggling with an Azure Function output binding that writes a queue message consisting of a list of identifiers. I experimented with a few different approaches before settling on the only viable solution. This post explains where I went wrong so that you can avoid making the same mistake.

But First, Some Context

Azure Functions are triggered by an event, such as an HTTP request, a Timer, or another of the numerous trigger types. The trigger’s associated data is provided as a parameter to the function:

[FunctionName("TimerTriggerFunction")]
public static void Run([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer)
{
    ...
}

In this case, the TimerInfo parameter provides contextual data related to the timer, such as the next scheduled run of the timer. The data type of the trigger parameter depends on the type of trigger being used for the function: TimerTriggers receive a TimerInfo object, HttpTriggers receive an HttpRequest object, etc.

Additionally, a function can receive and send data from input and output bindings that are mapped to other Azure services, like Storage. I like to think of bindings as a way to avoid writing boiler-plate code to read and write data from underlying data sources. Let’s look at an example.

My Azure Functions demo project has a function that is triggered on a timer every minute. The function validates data that is stored in an Azure Storage Table, with each row having a unique ID. Once the validations are complete, a message with the validated IDs needs to be written to an Azure Storage Queue.

In a traditional .NET application, I would need to code the logic to enqueue the message myself. This would typically involve creating a generic service that wraps the queue functionality, injecting the service where it’s needed, and calling the service’s enqueue method with the message in question.

An output binding removes much of that burden. Instead, I can return the value that I want written to the queue, as seen in the following sample, taken from Microsoft Docs:

[FunctionName("QueueOutput")]
[return: Queue("myqueue-items")]
public static string QueueOutput([HttpTrigger] dynamic input)
{
   return input.Text;
}

All I need to supply is a connection string and queue name in the function’s Application Settings, and the rest is taken care of for me.

However, the examples above use a string as return value, and I’m trying to write a list of integers to the queue. As we’ll see next, it’s easy to do just that, but I got so wrapped up in the output binding abstraction, that I forgot what the function is doing for me under the hood.

What Didn’t Work

The code samples below include only the code needed to illustrate the point I’m making. Obviously, it doesn’t compile, but it provides a clearer idea of what I’m trying to achieve. You can find the full source code here.

My first instinct was to return an array of integers from the function:

[return: Queue("validationscompleted")]
[FunctionName("Validator")]
public static async Task<int[]> Run([TimerTrigger("0 */1 * * * *")]TimerInfo myTimer)
{
    var flights = await _store.Get();
    ...
    return flights.Select(f => f.Id).ToArray();
}

The code compiles, but I get a runtime error from the WebJobs host. It’s complaining because primitive types are not supported for output bindings. In this case, I’m guessing the primitive type it’s referring to is the array.

So that’s a dead end. I then decided to try a list rather than an array:

[return: Queue("validationscompleted")]
[FunctionName("Validator")]
public static async Task<List<int>> Run([TimerTrigger("0 */1 * * * *")]TimerInfo myTimer, ILogger log)
{
    var flights = await _store.Get();
    ...
    return flights.Select(f => f.Id).ToList();
}

The code compiles once again, and I don’t get a runtime exception during the binding phase, but the function fails once its timer expires:

Error while handling parameter $return after function returned:. Newtonsoft.Json: Object serialized to Array. JObject instance expected. 

Another dead end. But this error message hints at the core of the problem, and the actual solution.

What Worked

Ultimately, the solution was straightforward: wrap whichever implementation of IEnumerable I want to use into an object that can be serialized to the queue.

[return: Queue("validationscompleted")]
[FunctionName("Validator")]
public static async Task<ValidationsComplete> Run([TimerTrigger("0 */1 * * * *")]TimerInfo myTimer)
{
    var flights = await _store.Get();
    ... 
    return new ValidationsComplete() {FlightIds = flights.Select(f => f.Id).ToList() , Successful = true  } ;
}

public class ValidationsComplete
{
    public bool Successful { get; set; }
    public IEnumerable<int> FlightIds { get; set; }
}

Here’s what the message looks like once it’s written to the Azure Queue.

Azure Queue Message Output Binding from Azure Function

At this point, the light bulb turned on, and it became obvious why the other approaches couldn’t possibly work: the output binding abstracts so many details that I quite literally forgot that I should be serializing a plain old C# object to the queue.

Hopefully this post clarifies the role of input and output bindings in Azure Functions, and helps you avoid making the same mistakes I did when using them. I ran into a few other gotchas while working on this, which I’ll be documenting over the coming weeks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s