Watch the recording of this lesson on YouTube 🎥.
The goal of this lesson is to learn how to table input and output bindings work.
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.
| Prerequisite | Exercise |
|---|---|
| Azure Storage Emulator or Storage account in Azure | 1-5 |
| Azure Storage Explorer | 1-5 |
| VSCode | 2-5 |
| VSCode AzureFunctions extension | 2-5 |
| Azure Functions Core Tools | 2-5 |
| RESTClient for VSCode | 2-5 |
See the prerequisites page for more details.
In this exercise we'll look into storage emulation and the Azure Storage Explorer to see how you can interact with tables and entities.
-
Make sure that the storage emulator is running and open the Azure Storage Explorer.
-
Navigate to
Storage Accounts->(Emulator - Default Ports)(Key)->Tables. -
Right-click
Tablesand selectCreate Table. -
Type a name for the table:
players -
Select the new table.
🔎 Observation - Now you see the contents of the table (which is still empty). In the top menu you see actions you can perform on the table or its records (entities).
-
Try adding a record to the table, you can use the following data:
-
PartitionKey: United Kingdom (string)
-
RowKey: 52a3be19-dc1d-4f29-84a6-1013fcfddfa3 (string)
-
Id: 52a3be19-dc1d-4f29-84a6-1013fcfddfa3 (string)
-
NickName: Ada (string)
-
Email: ada@lovelace.org (string)
-
Region: United Kingdom (string)
🔎 Observation You'll see that the
PartitionKeyandRowKeyvalues are also available in theRegionandIdfields respectively. This type of 'entity modelling' is not required for Table entities. This is just they way we prefer to structure our data. We identify the fields in the business domain we want to use as keys and keep the original fields and their values as is. An alternative would be to only keep thePartitionKeyandRowKeyvalues and not include theIdandRegionfields. But then you need a bit more mapping in your domain classes to map to theIdandRegionfields again.📝 Tip - use the
Add Propertybutton to add new fields to the entity.
-
In this exercise, we'll be creating an HttpTrigger function and use the Table output binding with a type based on TableEntity in order to put player data 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: AzureFunctionsUniversity.Demo
- AccessRights: Function
-
Once the Function App is generated, add a reference to these NuGet packages:
Microsoft.Azure.WebJobs.Extensions.Storage. This allows us to use bindings for Blobs, Tables and Queues.Microsoft.Azure.Cosmos.Table. This allows us to use theTableEntitytype as a basis for our customPlayerEntitytype.
📝 Tip - One way to install packages is to use the NuGet Package Manager VSCode extension:
- Run
NuGet Package Manager: Add new Packagein the Command Palette (CTRL+SHIFT+P). - Type the name of the package (e.g.
Microsoft.Azure.WebJobs.Extensions.Storage). - Select the most recent (non-preview) version of the package.
-
We'll be working with a
PlayerEntitytype, similar to thePlayertype used in the Blob and Queue lessons. However, that exact same class can't be used here since we need to use thePartitionKeyandRowKeyproperties Table Storage requires.-
Create a new file to the project, called
PlayerEntity.cs. -
Copy/paste this content into it.
🔎 Observation - Look at the
PlayerEntityclass. Notice that it inherits fromTableEntity. This type comes from theMicrosoft.Azure.Cosmos.TableNuGet package. Entities require a default, public parameterless, constructor (for proper (de)serialization). If you don't provide one you'll get errors such as;Table entity types must provide a default constructor.. In addition to the default constructor there is a constructor which sets all properties including thePartitionKeyandRowKeybased on the region and ID of the player. The keys are passed to the base class, theTableEntity. Finally, note that there is aSetKeys()method. This method will be used in the functions we'll write in this lesson, in order to set thePartitionKeyandRowKey. We're not constructing a newPlayerEntityusing the constructor, but updating an incomplete entity, which we'll receive from the HTTP request body.
-
-
Now update the function method HttpTrigger argument so it looks like this:
[HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Post), Route = null)] PlayerEntity playerEntity)
🔎 Observation - We expect that a
PlayerEntitytype will be posted to this HTTP endpoint. Assume that thePartitionKeyandRowKeyproperties are not provided as part of the JSON object in the request. We'll deal with those later. -
We haven't specified the table name yet. Lets add a new file, called
TableConfig.csand copy the following into the file:namespace AzureFunctionsUniversity.Table { public static class TableConfig { public const string Table = "players"; } }
🔎 Observation - Now we can refer to the table name by using
TableConfig.Table. -
Back in the function class, add the following return attribute just below the
FunctionNameattribute:[return: Table(TableConfig.Table)]
🔎 Observation - We've now defined that we return the output from the function to a table which name is configured in the
TableConfigclass.🔎 Observation - Notice that we're not specifying the
Connectionproperty for theTablebinding. This means the storage connection of the Function App itself is used for Table storage. It now uses the"AzureWebJobsStorage"setting in thelocal.settings.jsonfile. 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. For production workloads it's recommended to use a separate Storage Account for your data. -
Remove the entire content of the function method and replace it with these two lines:
playerEntity.SetKeys() return playerEntity;
❔ Question - We're calling the
SetKeys()method on thePlayerEntityclass. Why are we doing this before we return the entity to the table? -
Verify that the entire function method looks as follows:
[FunctionName(nameof(StorePlayerReturnAttributeTableOutput))] [return: Table(TableConfig.Table)] public static PlayerEntity Run( [HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Post), Route = null)] PlayerEntity playerEntity) { playerEntity.SetKeys() return playerEntity; }
-
Ensure that the storage emulator is started. Then build & run the
AzureFunctions.TableFunction 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/StorePlayerReturnAttributeTableOutput Content-Type: application/json { "id": "{{$guid}}", "nickName" : "Frances", "email" : "frances@northcutt.org", "region" : "United States of America" }
❔ Question - Look at the Azure Functions console output. Is the function executed without errors?
❔ Question - Using the Azure Storage Explorer, check if there's a new entity in the
playerstable. If so, click on the entity and inspect its properties.
In this exercise, we'll be adding an HttpTrigger function and use the Table output binding with the IAsyncCollector<PlayerEntity> output type in order to store multiple player entities in the players table when the HTTP request contains an array of Player objects.
-
Create a copy of the
StorePlayerReturnAttributeTableOutput.csfile and rename the file, the class and the function toStorePlayersWithAsyncCollectorTableOutput.cs. -
We won't be using the return attribute in this function so remove the line with
[return: Table(TableConfig.Table)]. -
Change the
Runmethod signature from:public static PlayerEntity Run
to
public static async Task<IActionResult> Run
-
Since the method needs to work with an array of
PlayerEntityelements, change the input type from:PlayerEntity playerEntity
to
PlayerEntity[] playerEntities
-
Add the following
Tableoutput binding to the method:[Table(TableConfig.Table)] IAsyncCollector<PlayerEntity> collector
📝 Tip - The
IAsyncCollector<T>andICollector<T>interfaces are supported by several output bindings such as Queue, Table, 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. -
Replace the content of the
Runmethod with this code:foreach (var playerEntity in playerEntities) { playerEntity.SetKeys(); await collector.AddAsync(playerEntity); } return new AcceptedResult();
-
Verify that the entire function looks like this now:
public static class StorePlayersWithAsyncCollectorTableOutput { [FunctionName(nameof(StorePlayersWithAsyncCollectorTableOutput))] public static async Task<IActionResult> Run( [HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Post), Route = null)] PlayerEntity[] playerEntities, [Table(TableConfig.Table)] IAsyncCollector<PlayerEntity> collector) { foreach (var playerEntity in playerEntities) { playerEntity.SetKeys(); await collector.AddAsync(playerEntity); } return new AcceptedResult(); } }
-
Ensure that the storage emulator is started. Then build & run the
AzureFunctions.TableFunction App. -
Do a POST request with an array of players to the function endpoint:
POST http://localhost:7071/api/StorePlayersWithAsyncCollectorTableOutput Content-Type: application/json [ { "id": "{{$guid}}", "nickName" : "Grace", "email" : "grace@hopper.org", "region" : "United States of America" }, { "id": "{{$guid}}", "nickName" : "Margaret", "email" : "margaret@hamilton.org", "region" : "United States of America" }, { "id": "{{$guid}}", "nickName" : "Mary", "email" : "mary@jackson.org", "region" : "United States of America" } ]
❔ Question - Look at the Azure Functions console output. Is the function executed without errors?
❔ Question - Using the Azure Storage Explorer, are there several new entities in the
playerstable?
In this exercise, we'll be adding an HttpTrigger function and use the Table input binding with the PlayerEntity type in order to retrieve one player entity from the players table. We'll be doing a point query, which means we use both the PartitionKey and RowKey in order ot retrieve a single entity from the table. In this case we'll provide the player region (PartitionKey) and the player ID (RowKey), both will be part of the route.
-
Create a copy of the
StorePlayerReturnAttributeTableOutput.csfile and rename the file, the class and the function toGetPlayerByRegionAndIdCloudTableInput.cs. -
We won't be using the return attribute in this function so remove the line with
[return: Table(TableConfig.Table)]. -
Update the HttpTrigger attribute as follows:
[HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Get), Route = "GetPlayerByRegionAndIdTableInput/{region}/{id}")] HttpRequest request,
🔎 Observation - Note that Route parameter contains {region} and {id} expressions.
-
Add the following Table input binding as the final parameter of the method:
[Table( TableConfig.Table, "{region}", "{id}")] PlayerEntity playerEntity)
🔎 Observation - Note that the Table input binding uses the exact same {region} and {id} expressions as the HttpTrigger. This will result in a point query on the table that returns a single entity.
-
Update the body of the function with:
return new OkObjectResult(playerEntity);
-
Verify that the entire function looks like this:
[FunctionName(nameof(GetPlayerByRegionAndIdTableInput))] public static IActionResult Run( [HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Get), Route = "GetPlayerByRegionAndIdTableInput/{region}/{id}")] HttpRequest request, [Table( TableConfig.Table, "{region}", "{id}")] PlayerEntity playerEntity) { return new OkObjectResult(playerEntity); }
-
Ensure that the storage emulator is started. Then build & run the
AzureFunctions.TableFunction App. -
Ensure that there's at least one entity present in the
playersTable. Copy thePartitionKeyandRowKeyfor that entity to use in in the next step. -
Do a GET request to the endpoint and update the
PARTITION_KEYandROW_KEYfields with the values from the previous step:GET http://localhost:7071/api/GetPlayerByRegionAndIdTableInput/PARTITION_KEY/ROW_KEY
Example
GET http://localhost:7071/api/GetPlayerByRegionAndIdTableInput/United%20States%20of%20America/6449f9a2-56be-4f7c-a8ee-02603bb7625c
❔ Question - Does the function run without errors? Do you get the expected
PlayerEntityback?
In this exercise we'll create an HttpTrigger function which returns multiple PlayerEntity objects from the players table using a Table input binding based on the CloudTable type. The body of the function will use a TableQuery that uses information based on parameters from the HTTP query string (region and nickname).
-
Create a copy of the
GetPlayerByRegionAndIdTableInput.csfile and rename the file, the class and the function toGetPlayersByRegionAndNickNameCloudTableInput.cs. -
Update the HttpTrigger so the Route is
null:[HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Get), Route = null)] HttpRequest request
-
Remove the
regionandidparameters from the method. We'll be using the query string parameters this time. -
Update the Table input binding to this:
[Table(TableConfig.Table)] CloudTable cloudTable
🔎 Observation - The Table binding only uses the table name now. The type the binding is using is
CloudTableand comes from theMicrosoft.Azure.Cosmos.TableNuGet package. The CloudTable exposes a lot of methods to interact with a Table in either TableStorage or CosmosDB Tables, have a look! -
Replace the body of the function method with:
string region = request.Query["region"]; string nickName = request.Query["nickName"]; var regionAndNickNameFilter = new TableQuery<PlayerEntity>() .Where( TableQuery.CombineFilters( TableQuery.GenerateFilterCondition( nameof(PlayerEntity.PartitionKey), QueryComparisons.Equal, region), TableOperators.And, TableQuery.GenerateFilterCondition( nameof(PlayerEntity.NickName), QueryComparisons.Equal, nickName))); var playerEntities = cloudTable.ExecuteQuery<PlayerEntity>(regionAndNickNameFilter); return new OkObjectResult(playerEntities);
🔎 Observation - Note that the region and nickName are retrieved from the query string of the HTTP request.
🔎 Observation - Note that a
TableQuery<PlayerEntity>is created with filter conditions based on thePartitionKeyand theNickNameproperties of aPlayerEntity. The query is executed on theCloudTabletype that is tied to the Table input binding.❔ Question - Look into the
TableQueryclass. What other methods does it support?❔ Question - Look into the
QueryComparisonsclass. What other constants does it have?❔ Question - Look into the
TableOperatorsclass. What other constants does it have? -
Ensure that the storage emulator is started. Then build & run the
AzureFunctions.TableFunction App. -
Ensure that there are several entities present in the
playersTable. Copy thePartitionKeyandNickNameof an entity you want to return from the function. -
Do a GET request to the endpoint and update the
PARTITION_KEYandNICK_NAMEfields with the values from the previous step:GET http://localhost:7071/api/GetPlayersByRegionAndNickNameCloudTableInput ?region=PARTITION_KEY &nickName=NICK_NAME
Example
GET http://localhost:7071/api/GetPlayersByRegionAndNickNameCloudTableInput ?region=United States of America &nickName=Mary
❔ Question - Did the function run without errors? Are the correct entities returned?
Here is the assignment for this lesson.
For more info about the Table Trigger and binding have a look at the official Azure Functions Table Storage Bindings documentation. For details on Azure Table Storage look at this Table Storage Overview and this Table Storage Design Guide.
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.


