Watch the recording of this lesson on YouTube 🎥.
The goal of this lesson is to learn how to trigger a function by putting a message on a queue, and how you can bind an output message to a queue.
This lessons consists of the following exercises:
📝 Tip - If you're stuck at any point you can have a look at the source code in this repository.
📝 Tip - If you have questions or suggestions about this lesson, feel free to create a Lesson Q&A discussion here on GitHub.
In this exercise we'll look into storage emulation and the Azure Storage Explorer to see how you can interact with queues and messages.
-
Make sure that the storage emulator is running and open the Azure Storage Explorer.
-
Navigate to
Storage Accounts
->(Emulator - Default Ports)(Key)
->Queues
-
Right-click on
Queues
and selectCreate Queue
-
Type a name for the queue:
newplayer-items
-
🔎 Observation - Now you see the contents of the queue. In the top menu you see actions you can perform on the queue or its items.
-
Try adding three messages to the queue, each with different content.
-
Now try dequeue-ing the messages.
❔ Question - What do you notice about the order of the messages being dequeued?
In this exercise, we'll be creating an HttpTrigger function and use the Queue output binding with a string
type in order to put player messages on the newplayer-items
queue.
-
In VSCode, create a new HTTP Trigger Function App with the following settings:
- Location: AzureFunctions.Queue
- Language: C#
- Template: HttpTrigger
- Function name: NewPlayerWithStringQueueOutput
- Namespace: AzureFunctionsUniversity.Demo
- AccessRights: Function
-
Once the Function App is generated, add a reference to the
Microsoft.Azure.WebJobs.Extensions.Storage
NuGet package to the project. This allows us to use bindings for Blobs, Tables and Queues.📝 Tip - One way to easily do this is to use the NuGet Package Manager VSCode extension:
- Run
NuGet Package Manager: Add new Package
in the Command Palette (CTRL+SHIFT+P). - Type:
Microsoft.Azure.WebJobs.Extensions.Storage
- Select the most recent (non-preview) version of the package.
- Run
-
We'll be working with the same
Player
type again as we did in the Blob lesson. Create a new file to the project, calledPlayer.cs
, and copy/paste this content into it. -
Now update the function method HttpTrigger argument so it looks like this:
[HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Post), Route = null)] Player player
🔎 Observation - The function will only allow POST requests and since we're using the
Player
type directly, we don't have to extract it from the request content ourselves.📝 Tip - When you use the
HttpMethods
enumeration you need to add a using to theMicrosoft.AspNetCore.Http
namespace. -
Remove the entire content of the function method and replace it with this single line:
return JsonConvert.SerializeObject(player);
-
Update the return type of the
Run
method tostring
, so the method declaration looks like this:public static string Run(...)
-
We haven't specified how the player data will be put on the queue. First, lets add a new file, called
QueueConfig.cs
and copy the following into the file:namespace AzureFunctionsUniversity.Demo.Queue { public static class QueueConfig { public const string NewPlayerItems = "newplayer-items"; } }
🔎 Observation - We've now captured the name of the output queue in a string constant. We can use this constant whenever we need to refer to the name of the queue.
-
Now let's use the newly created
QueueConfig
to configure what the output of our function is. Add the following underneath theFunctionName
attribute (so directly above theRun
method):[return: Queue(QueueConfig.NewPlayerItems)]
🔎 Observation - We've just used a
Queue
attribute to specify this method will return a message to a queue name that is specified inQueueConfig.NewPlayerItems
.🔎 Observation - Notice that we're not specifying the Connection property for the
Blob
binding. This means the storage connection of the Function App itself is used for the Blob storage. It now uses the"AzureWebJobsStorage"
setting in thelocal.settings.json
file. The value of this setting should be:"UseDevelopmentStorage=true"
when emulated storage is used. When an Azure Storage Account is used this value should contain the connection string to that Storage Account. -
Verify that the entire function method looks as follows:
[FunctionName(nameof(NewPlayerWithStringQueueOutput))] [return: Queue(QueueConfig.NewPlayerItems)] public static string Run( [HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Post), Route = null)] Player player) { return JsonConvert.SerializeObject(player); }
-
Ensure that the storage emulator is started. Then build & run the
AzureFunctions.Queue
Function App.📝 Tip - When you see an error like this:
Microsoft.Azure.Storage.Common: No connection could be made because the target machine actively refused it.
that means that the Storage Emulator has not been started successfully and no connection can be made to it. Check the app settings in the local.settings.json and (re)start the emulated storage. -
Do a POST request to the function endpoint:
POST http://localhost:7071/api/NewPlayerWithStringQueueOutput Content-Type: application/json { "id": "{{$guid}}", "nickName" : "Ada", "email" : "[email protected]", "region" : "United Kingdom" }
❔ Question - Look at the Azure Functions console output. Is the message processed without errors?
❔ Question - Using the Azure Storage Explorer, check if there's a new message in the
newplayer-items
queue. What is the content of the message?
In this exercise, we'll be adding an HttpTrigger function and use the Queue output binding with the Player
output type in order to put player messages on the newplayer-items
queue.
-
Create a copy of the
NewPlayerWithStringQueueOutput.cs
file and rename the file, the class and the function toNewPlayerWithTypedQueueOutput.cs
. -
Remove the
[return: Queue(QueueConfig.NewPlayerItems)]
line since we won't be using the same return Queue attribute. -
Instead add the following Queue binding underneath the HttpTrigger:
[Queue(QueueConfig.NewPlayerItems)] out Player playerOutput
🔎 Observation - Here we're specifying a Queue output binding with the same queue name as before. But now we're using an
out
parameter of typePlayer
. Now the return type of the function itself can be of typeIActionResult
, so we have more control of what kind of response we sent back to the caller of our function. -
Update the return type of the method to
IActionResult
, so the method declaration looks like this:public static IActionResult Run(...)
-
Replace the contents of the function method with the following:
IActionResult result = null; playerOutput = null; if (string.IsNullOrEmpty(player.Id)) { result = new BadRequestObjectResult("No player data in request."); } else { playerOutput = player; result = new AcceptedResult(); } return result;
🔎 Observation - Based on the presence of a Player ID we return either a BadRequestObjectResult or an AcceptedResult. When the player object is valid it is assigned to the playerOutput object, and that object will be put on the queue.
-
Verify that the entire function method looks as follows:
[FunctionName(nameof(NewPlayerWithTypedQueueOutput))] public static IActionResult Run( [HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Post), Route = null)] Player player, [Queue(QueueConfig.NewPlayerItems)] out Player playerOutput) { IActionResult result = null; playerOutput = null; if (string.IsNullOrEmpty(player.Id)) { result = new BadRequestObjectResult("No player data in request."); } else { playerOutput = player; result = new AcceptedResult(); } return result; }
-
Build & run the
AzureFunctions.Queue
Function App. -
Do a POST request, with a complete player object, to the function endpoint:
POST http://localhost:7071/api/NewPlayerWithTypedQueueOutput Content-Type: application/json { "id": "{{$guid}}", "nickName" : "Grace", "email" : "[email protected]", "region" : "United States of America" }
❔ Question - > Is there a new message in the
newplayer-items
queue? -
Do a POST request, without a player ID, to the function endpoint:
POST http://localhost:7071/api/NewPlayerWithTypedQueueOutput Content-Type: application/json { "nickName" : "Grace", "email" : "[email protected]", "region" : "United States of America" }
❔ Question - > Do you receive the correct HTTP response? Is there a new message in the
newplayer-items
queue now?
In this exercise, we'll be adding an HttpTrigger function and use the Queue output binding with the CloudQueueMessage
output type in order to put player messages on the newplayer-items
queue.
-
Create a copy of the
NewPlayerWithTypedQueueOutput.cs
file and rename the file, the class and the function toNewPlayerWithCloudQueueMessageOutput.cs
. -
We'll be using a new output type, called
CloudQueueMessage
. To use the latest version of this type add a reference to theAzure.Storage.Queues
NuGet package to the project.📝 Tip - One way to easily do this is to use the NuGet Package Manager VSCode extension:
- Run
NuGet Package Manager: Add new Package
in the Command Palette (CTRL+SHIFT+P). - Type:
Azure.Storage.Queues
- Select the most recent (non-preview) version of the package.
- Run
-
Change the output type of the Queue binding from:
out Player playerOutput
to
out CloudQueueMessage message
📝 Tip - Ensure that the
CloudQueueMessage
type is from the newMicrosoft.Azure.Storage.Queue
namespace and not the oldMicrosoft.WindowsAzure.Storage.Queue
namespace. -
Now that we have defined a new output parameter named
message
we still need to set it with player data. Replace:playerOutput = null;
with:
message = null;
-
A
CloudQueueMessage
is constructed with abyte
array, orstring
as the message content. Let's use thestring
content option so serialize thePlayer
object. Put the following code inside theelse
statement:var serializedPlayer = JsonConvert.SerializeObject(player); message = new CloudQueueMessage(serializedPlayer); result = new AcceptedResult();
-
Build & run the
AzureFunctions.Queue
Function App. -
Make a POST call to the
NewPlayerWithCloudQueueMessageOutput
endpoint and provide a valid json body with aPlayer
object:POST http://localhost:7071/api/NewPlayerWithCloudQueueMessageOutput Content-Type: application/json { "id": "{{$guid}}", "nickName" : "Ada", "email" : "[email protected]", "region" : "United Kingdom" }
❔ Question - Inspect the
newplayer-items
queue. does it contain a new message?
In this exercise, we'll be adding an HttpTrigger function and use dynamic output bindings in order to put valid player messages on the newplayer-items
queue, and invalid messages on a newplayer-error-items
queue.
📝 Tip - Dynamic bindings are useful when output or input bindings can only be determined at runtime. In this case we'll use the dynamic binding to determine the queue name at runtime.
-
Create a copy of the
NewPlayerWithTypedQueueOutput.cs
file and rename the file, the class and the function toNewPlayerWithDynamicQueueOutput.cs
. -
Replace the line with the
Queue
binding attribute and parameter with:IBinder binder
-
Remove the contents of the method.
-
Now add the following sections to the method:
-
First, initialize the result of the HTTP response and the name of the queue:
IActionResult result; string queueName;
-
Second, add an
if/else
statement to determine the name of the queue and the HTTP response:if (string.IsNullOrEmpty(player.Id)) { queueName = QueueConfig.NewPlayerErrorItems; result = new BadRequestObjectResult("No player data in request."); } else { queueName = QueueConfig.NewPlayerItems; result = new AcceptedResult(); }
-
Then, serialize the
Player
object and create an instance of aCloudQueueMessage
. The cloudQueueMessage will be used in the dynamic queue binding:var serializedPlayer = JsonConvert.SerializeObject(player); var cloudQueueMessage = new CloudQueueMessage(serializedPlayer);
📝 Tip - Make sure you use the CloudQueueMessage from the
Microsoft.Azure.Storage.Queue
namespace and not theMicrosoft.WindowsAzure.Storage.Queue
namespace (the latter one is outdated). A dependency to theAzure.Storage.Queues
NuGet package is required for this. -
Finally, create a new
QueueAttribute
, create an instance of aCloudQueue
using the binder interface and add the cloudQueueMessage to the queue as follows:var queueAttribute = new QueueAttribute(queueName); var cloudQueue = await binder.BindAsync<CloudQueue>(queueAttribute); await cloudQueue.AddMessageAsync(cloudQueueMessage); return result;
❔ Question - Look into the
CloudQueue
type. Which other operations does this type have?
-
-
Build & run the
AzureFunctions.Queue
Function App. -
First make a POST call to the
NewPlayerWithDynamicQueueOutput
endpoint and provide a valid json body with aPlayer
object:POST http://localhost:7071/api/NewPlayerWithDynamicQueueOutput Content-Type: application/json { "id": "{{$guid}}", "nickName" : "Ada", "email" : "[email protected]", "region" : "United Kingdom" }
❔ Question - Inspect the
newplayer-items
queue. Does it contain a new message? -
Now make a POST call to the
NewPlayerWithCloudQueueMessageOutput
endpoint and provide an invalid player json body as follows:POST http://localhost:7071/api/NewPlayerWithDynamicQueueOutput Content-Type: application/json { "nickName" : "Ada", }
❔ Question - Inspect the
newplayer-error-items
queue. Does it contain a new message?
In this exercise, we'll be adding an HttpTrigger function and use the Queue output binding with the IAsyncCollector<Player>
output type in order to put multiple player messages on the newplayer-items
queue when the HTTP request contains an array of Player
objects.
-
Create a copy of the
NewPlayerWithTypedQueueOutput.cs
file and rename the file, the class and the function toNewPlayerWithIAsyncCollectorQueueOutput.cs
. -
Update the type of the Queue attribute and replace:
out Player playerOutput
with
IAsyncCollector<Player> collector
📝 Tip - The
IAsyncCollector<T>
andICollector<T>
interfaces are supported by several output bindings such as Queue, ServiceBus, and EventHubs. When this interface is used, items are added to the (in-memory) collector and not directly to the target service behind the output binding. Once the collector is flushed, either using a direct method call or automatically when the function completes, the items in the collector are transferred. -
Update the
HTTPTrigger
input type from:Player player
to
Player[] players
🔎 Observation - Notice that we expect an array of Player items in our request.
-
Remove the contents of the function method.
-
Update the function method as follows: Check if there are player objects, iterate over them, add the objects to the collector, and return a
AcceptedResult
. If there are no players to process, return aBadRequestObjectResult
:IActionResult result = null; if (players.Any()) { foreach (var player in players) { await collector.AddAsync(player); } result = new AcceptedResult(); } else { result = new BadRequestObjectResult("No player data in request."); } return result;
-
Build & run the
AzureFunctions.Queue
Function App. -
Do a POST call to the
NewPlayerWithIAsyncCollectorQueueOutput
endpoint and provide a valid json body with multiplePlayer
objects:POST http://localhost:7071/api/NewPlayerWithIAsyncCollectorQueueOutput Content-Type: application/json [ { "id": "{{$guid}}", "nickName" : "Grace", "email" : "[email protected]", "region" : "United States of America" }, { "id": "{{$guid}}", "nickName" : "Ada", "email" : "[email protected]", "region" : "United Kingdom" }, { "id": "{{$guid}}", "nickName" : "Margaret", "email" : "[email protected]", "region" : "United States of America" } ]
❔ Question - Have a look at the
newplayer-items
queue. Does it contain multiple new messages?
In this exercise we'll create a new QueueTriggered function and trigger it with a message.
-
Create a new Function App by running
AzureFunctions: Create New Project
in the VSCode Command Palette (CTRL+SHIFT+P).📝 Tip - Create a folder with a descriptive name since that will be used as the name for the project, e.g.
AzureFunctionsUniversity.Queue
. -
Select the language you'll be using to code the function, in this lesson we'll be using
C#
. -
Select
QueueTrigger
as the template. -
Give the function a name (e.g.
HelloWorldQueueTrigger
). -
Enter a namespace for the function (e.g.
AzureFunctionsUniversity.Demo
). -
Select
Create a new local app setting
.🔎 Observation - The local app settings file (local.settings.json) is used to store environment variables and other useful configurations.
-
Select the Azure subscription you will be using.
-
Since we are using the QueueTrigger, we need to provide a storage account, select one or create a new storage account.
- If you select a new one, provide a name (we chose
azfuncstor
). The name you provide must be unique to all Azure.
- If you select a new one, provide a name (we chose
-
Select a resource group or create a new one.
- If you create a new one, you must select a region. Use the one closest to you.
-
Enter the name of the storage queue, you can leave the default value
myqueue-items
if you'd like or change it. Make sure to keep this in mind as we will be referencing it later on. -
When asked about storage required for debugging choose Use local emulator.
Now the Function App with a Queue Trigger function will be created.
Great, we've got our Function Project and Queue Trigger created, let's examine what has been generated for us.
public static class HelloWorldQueueTrigger
{
[FunctionName("HelloWorldQueueTrigger")]
public static void Run(
[QueueTrigger(
"myqueue-items",
Connection = "azfuncstor_STORAGE")]string myQueueItem,
ILogger log)
{
log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
}
}
🔎 Observation - The
QueueTrigger
indicates this function will be triggered based on queue messages. The first parameter in this attribute is the name of the queue,myqueue-items
. TheConnection
parameter contains the name of the application setting which contains the connection string. In this case a setting calledazfuncstor_STORAGE
should be present in thelocal.settings.json
. If you choose to use local storage emulation instead of the Azure Storage Account you can change the value of theazfuncstor_STORAGE
setting toUseDevelopmentStorage=true
;
🔎 Observation - The queue message itself, named
myQueueItem
, is read as a string and outputted to the log inside the method.
-
Build and run the Function App.
-
The function will only be triggered when a message is put on the
myqueue-items
queue. Use the Azure Storage Explorer to add a message to this queue.❔ Question - Is the function triggered once you've put a message on the queue? How can you determine this?
📝 Tip - You can configure the behavior of the queue binding via the
host.json
file. Configurable settings include the frequency of polling the queue for new messages, timeout duration when processing fails, and how many messages the function will process in parallel. See the official docs for the details.
Now that the queue trigger is working, let's break it! When a queue triggered function can't process the message successfully, the message will be retried a couple of time (5 times by default). When it still fails after the final retry the message will be placed on a so-called poison queue. This is a new queue dedicated for messages that can't be processed. The name of this queue is the same as the regular queue but ends with -poison
.
-
Replace the contents of the queue triggered function with this exception:
throw new Exception("Uh oh!");
🔎 Observation - Throwing an exception will force the function to fail processing the message. The function will retry to process the message a couple of times.
-
Build and run the Function App. Put a breakpoint on the
throw new Exception()
line. -
Using the Azure Storage Explorer, add a message to the
myqueue-items
queue.❔ Question - How many times is the breakpoint hit?
❔ Question - Once the function has stopped retrying, is there a
myqueue-items-poison
queue? Does it contain the message you created?
Now you understand how queue triggers work, let's do something useful with the message. Let's add a Blob output binding which saves the contents of the message to a Blob container. We'll use a Player json object as the message this time.
-
The Azure Functions project template usually references slightly outdated NuGet packages (the release cycle of these NuGet packages is shorter than that of the project template). Let's update these references so we're working with the latest bits. Update the
Microsoft.Azure.WebJobs.Extensions.Storage
andMicrosoft.NET.Sdk.Functions
NuGet packages to the most recent (non-preview) versions. -
Add a reference to the
Azure.Storage.Blobs
NuGet package to the project. Use the most recent (non-preview) version. -
Add the following Blob output binding to the method:
[Blob("players", FileAccess.Write)] CloudBlobContainer blobContainer,
📝 Tip - The
CloudBlobContainer
type is part of theMicrosoft.Azure.Storage.Blob
namespace. -
Add the
Player.cs
class, used in earlier exercises, to the project. -
Replace the existing body of the function method with the following:
var player = JsonConvert.DeserializeObject<Player>(message); var blob = blobContainer.GetBlockBlobReference($"player-{player.Id}.json"); await blob.UploadTextAsync(message);
🔎 Observation - First the queue message will be converted to a Player object. Then a new blob reference is created where the blob name is based on the player Id. Finally the message content is uploaded to Blob Storage. Since an asynchronous method (
UploadTextAsync
) is used, the function method signature need to change fromvoid
toasync Task
. A reference to theSystem.Threading.Tasks
namespace is required for this. -
The entire function method should look like this:
[FunctionName("HelloWorldQueueTrigger")] public static async Task Run( [QueueTrigger( "myqueue-items", Connection = "azfuncstor_STORAGE")]string message, [Blob("players", FileAccess.Write)] CloudBlobContainer blobContainer, ILogger log) { var player = JsonConvert.DeserializeObject<Player>(message); var blob = blobContainer.GetBlockBlobReference($"player-{player.Id}.json"); await blob.UploadTextAsync(message); }
-
Build and run the Function App.
-
Using the Azure Storage Explorer, add a message with the following json content to the
myqueue-items
queue:{ "id":"2673d898-a6b4-4cef-92b0-8fc46bcf972f", "nickName":"Margaret", "email":"[email protected]", "region":"United States of America" }
❔ Question - Is the function triggered by the message? Is a new blob available in the "players" Blob container?
Here is the assignment for this lesson.
For more info about the Queue Trigger and binding have a look at the official Azure Functions Queue Storage and Bindings documentation.
We love to hear from you! Was this lesson useful to you? Is anything missing? Let us know in a Feedback discussion post here on GitHub.