The goal of this lesson is to learn how to table input and output bindings work.
This lesson 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.
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. If you use GitHub CodeSpaces, then use the Azure extension, Workspace option.
-
Navigate to
Storage Accounts
->(Emulator - Default Ports)(Key)
->Tables
Or when using the CodeSpace, useWorkspace / Local Emulator / Tables
-
Right click on
Table
and selectCreate Table
-
Type a name for the table:
players
-
🔎 Observation - Now you see the contents of the table. In your case it is still empty. In the top menu you see actions you can perform on the table or its entities.
-
Try adding a record to the table.
❔ Question - What are the two mandatory fields to provide values for?
📝 Tip - The
PartitionKey
is used to determine in which partition the entity is stored, and is crucial for scalability and effective querying of the entities. TheRowKey
is a unique identifier of tht entity within the partition. The combination of thePartitionKey
and theRowKey
is the primary key of the entity. More info about partition and row keys can be found in the official docs.
In this exercise we'll be using a return Table
attribute as the output binding, to store a PlayerEntity
in the players
table.
-
In VSCode, create a new HTTP Trigger Function App with the following settings:
- Location: AzureFunctions.Table
- Language: C#
- Template: HttpTrigger
- Function name: StorePlayerReturnAttributeTableOutput
- Namespace: AzureFunctions.Demo
- AccessRights: Function
-
Once the Function App is generated, add a reference to the
Microsoft.Azure.WebJobs.Extensions.Tables
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.Tables
- Select the most recent (non-preview) version of the package.
- Run
-
We'll be working with a
PlayerEntity
type in the exercises. Add a new class file to the project namedPlayerEntity.cs
and copy the contents from this file in it.📝 Tip The
PlayerEntity
object inherits fromTableEntity
. Make sure you reference theTableEntity
type from theMicrosoft.Azure.Cosmos.Table
namespace and not theMicrosoft.WindowsAzure.Storage.Table
since the last one is outdated.🔎 Observation The
TableEntity
type contains all the mandatory properties an entity has, such as thePartitionKey
,RowKey
, andTimeStamp
. Inheriting from this type is not mandatory, as long as your custom type has the two Key properties, but it makes it easier to work with Table Storage. -
To make it easier to refer to the table name (
players
) we can put it in a constant in a new class.-
Add a new file to project called
TableConfig.cs
-
Add the following code to this class:
namespace AzureFunctions.Table { public static class TableConfig { public const string Table = "players"; } }
-
-
Now let's go back to the function and put the following line underneath the
FunctionName
attribute:[return: Table(TableConfig.Table)]
-
Update the
HttpTrigger
attribute so it only acceptsPOST
requests. -
Since we'll be returning a
PlayerEntity
instead of anIActionResult
, update the method return type to:public static async Task<PlayerEntity> Run(...)
-
Replace the entire contents of the function method with these lines:
var playerEntity = await message.Content.ReadAsAsync<PlayerEntity>(); playerEntity.SetKeys(); return playerEntity;
❔ Question - Notice the use of the
SetKeys()
method. What is the purpose of this method? -
Make sure the storage emulator is running, then build & run the
AzureFunctions.Table
Function App.❔ Question - Have a look at the runtime output. Did the Function App startup successfully?
-
Make a POST call to the
StorePlayerReturnAttributeTableOutput
endpoint and provide a valid json body with aPlayerEntity
object (thePartitionKey
andRowKey
are not required):POST http://localhost:7071/api/StorePlayerReturnAttributeTableOutput Content-Type: application/json { "id": "{{$guid}}", "nickName" : "Ada", "email" : "[email protected]", "region" : "United Kingdom" }
❔ Question - Have a look at the runtime output. Did the function execute successfully?
❔ Question - Now use the Storage Explorer and look at the
players
table. Is there a new record? If so, open it to inspect it.
In this exercise we'll be using a IAsyncCollector<PlayerEntity>
table output binding to store multiple PlayerEntity
objects in the players
table.
-
Create a copy of the
StorePlayerReturnAttributeTableOutput.cs
file and rename the file, the class and the function toStorePlayersWithCollectorTableOutput.cs
. -
We won't use a return attribute this time, so remove the line with
[return: Table(TableConfig.Table)]
. -
Change the return type of the method to
Task<IActionResult>
. -
Since we expect an HTTP request with an array of
PlayerEntity
elements, update theHttpTrigger
input type from:Player player
to
PlayerEntity[] playerEntities
-
Now add the following table binding underneath the
HTTPTrigger
:[Table(TableConfig.Table)] IAsyncCollector<PlayerEntity> collector
-
Replace the content of the function with this:
foreach (var playerEntity in playerEntities) { playerEntity.SetKeys(); await collector.AddAsync(playerEntity); } return new AcceptedResult();
🔎 Observation We're iterating over the player items in the playerEntities array. The PartitionKey and RowKeys are set for each item, then the item is added to the collector. The items are only stored to the table when the function completes or when
FlushAsync()
on the collector is called explicitly. -
Make sure the storage emulator is running, then build & run the
AzureFunctions.Table
Function App. -
Make a POST call to the
StorePlayersWithCollectorTableOutput
endpoint and provide a valid json body with multiplePlayerEntity
objects:POST http://localhost:7071/api/StorePlayersWithCollectorTableOutput 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 - Now use the Storage Explorer and look at the
players
table. Is there a new record? If so, open it to inspect it.
In this exercise we'll be using a TableEntity
type as the table input binding where we specify both the PartitionKey
and the RowKey
to retrieve one PlayerEntity
from the players
table and return it in the HTTP response.
-
Create a copy of the
StorePlayersWithCollectorTableOutput.cs
file and rename the file, the class and the function toGetPlayerByRegionAndIdPlayerEntityInput.cs
. -
Change the return type from async
Task<IActionResult>
toIActionResult
since the method will not be asynchronous. -
Update the
HttpTrigger
as follows:[HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Get), Route = "players/{region}/{id}")] HttpRequest request,
🔎 Observation Notice that we only accept GET requests. In addition, a custom route is now specified which contains a region and id. These correspond to the PartitionKey and RowKey of the entity we will retrieve.
-
We can't use the
region
andid
from the route unless we add these as separate parameters to the method:string region, string id,
-
Now update the
Table
binding to:[Table( TableConfig.Table, "{region}", "{id}")] PlayerEntity playerEntity
🔎 Observation In this table input binding three parameters are used: the table name, the PartitionKey, and the RowKey. This binding allows us to retrieve exactly one entity from the table.
-
Replace the contents of the function method with this single line:
return new OkObjectResult(playerEntity);
-
Make sure the storage emulator is running, then build & run the
AzureFunctions.Table
Function App. -
Ensure that there is at least one
PlayerEntity
in theplayers
table and make note of thePartitionKey
andRowKey
of this record. -
Make a GET call to the
StorePlayersWithCollectorTableOutput
@region = United States of America @id = 2c847c33-1c54-4c21-aa1a-a5c0a40f755a GET http://localhost:7071/api/players/{{region}}/{{id}}
🔎 Observation The
region
andid
fields need to match with an entity in yourplayers
table.❔ Question - Did the function return with a 200 OK, including the player data?
In this exercise we'll be using a TableClient
type as the table input binding, and retrieve multiple PlayerEntity
objects from the players
table and return it in the HTTP response.
-
Create a copy of the
GetPlayerByRegionAndIdPlayerEntityInput.cs
file and rename the file, the class and the function toGetPlayersByRegionCloudTableInput.cs
. -
We won't be using route parameters so remove the
/{region}/{id}
part of the Route:[HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Get), Route = "players")] HttpRequest request
-
Remove the
region
andid
parameters from the method parameters. -
Update the table input binding to it only uses the table name:
[Table(TableConfig.Table)] TableClient cloudTable
🔎 Observation Note that we're using the TableClient type, ensure that this type comes from the
Microsoft.Azure.Cosmos.Table
namespace. This type refers to a Table in either a Storage Account, or in CosmosDB. -
Replace the contents of the functions method with this:
string region = request.Query["region"]; var playerEntities = cloudTable.QueryAsync<PlayerEntity>(a=>a.PartitionKey == region); return new OkObjectResult(playerEntities);
🔎 Observation The region is extracted from the query string. The region is also the
PartitionKey
and will be used to query thePlayerEntity
objects within the region. -
Make sure the storage emulator is running, then build & run the
AzureFunctions.Table
Function App. -
Ensure that there are a couple of
PlayerEntity
objects in theplayers
table and decide whichPartitionKey
you'll use for querying. -
Make a GET call to the
GetPlayersByRegionCloudTableInput
@region = United States of America GET http://localhost:7071/api/players?region={{region}}
🔎 Observation The
region
field needs to match with an entity in yourplayers
table.❔ Question - Did the function return with a 200 OK, including the player data?
For more info about the Table Bindings have a look at the official Azure Functions Table Binding docs.