3
votes

Is it possible to (input) bind to table storage within an http-triggered function?

I'm attempting to add an input-binding to table-storage inside of a regular http-triggered function with the following attribute:

    [Table("MyTable", "MyPartition", "{httpTrigger}")] MyPoco poco

However it's returning the following error when I execute it:

[6/5/2019 5:36:38 PM] An unhandled host error has occurred. [6/5/2019 5:36:38 PM] Microsoft.Azure.WebJobs.Host: 'tableStorageInputBindingHttpTriggered' can't be invoked from Azure WebJobs SDK. Is it missing Azure WebJobs SDK attributes?.

Additionally at startup, I get this exception:

[6/5/2019 6:17:17 PM] tableStorageInputBindingHttpTriggered: Microsoft.Azure.WebJobs.Host: Error indexing method 'tableStorageInputBindingHttpTriggered'. Microsoft.Azure.WebJobs.Host: Unable to resolve binding parameter 'httpTrigger'. Binding expressions must map to either a value provided by the trigger or a property of the value the trigger is bound to, or must be a system binding expression (e.g. sys.randguid, sys.utcnow, etc.).

Here's the full function:

public class MyPoco
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public string Directory { get; set; }
}

public static class tableStorageInputBindingHttpTriggered
{
    [FunctionName("tableStorageInputBindingHttpTriggered")]
    public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    [Table("MyTable", "MyPartition", "{httpTrigger}")] MyPoco poco,
        ILogger log)
    {


        string name = req.Query["name"];

        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        dynamic data = JsonConvert.DeserializeObject(requestBody);
        name = name ?? data?.name;

        return name != null
            ? (ActionResult)new OkObjectResult($"PK={poco.PartitionKey}, RK={poco.RowKey}, Text={poco.Directory}")
            : new BadRequestObjectResult("");
    }
}

What am I doing wrong? How do I bind to table storage within an http-triggered azure-function?

3
Could you share your plan , what exactly you want to do?Md Farid Uddin Kiron
for every http request, i'd like the bbody of the request to be added to table storageAlex Gordon
Make me validate you want to read and write in table storage when invoke http trigger?Md Farid Uddin Kiron
@MdFaridUddinKiron i want to read (this is an input binding to table storage)Alex Gordon
Please take a look. I have tried this way. If it helps you. Sorry for my late response. Thanks and happy coding!Md Farid Uddin Kiron

3 Answers

6
votes

Issue is that http trigger returns you an object so it dont know how to extract your key.

You need to use route, which will tell Function how to get parameter and then you will be able to use that parameters

  public static async Task<HttpResponseMessage> SetLatestAsync(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "release-set-latest/{program}")]
            HttpRequestMessage req,
            string program,
            [Table(TableName, "latest", "{program}")]FlymarkLatestVersion pocos)
2
votes

Seems you are trying to read your Azure Table Storage from HTTP Trigger Function. Please see the code snippet below:

Your POCO Class:

  public class MyPoco
    {

        public string PartitionKey { get; set; }
        public string RowKey { get; set; }
        public string Directory { get; set; }

    }

Table Storage Class:

 public class TableStorageClass
    {
        public TableStorageClass()
        {

        }
        public TableStorageClass(DynamicTableEntity entity)
        {
            PartitionKey = entity.PartitionKey;
            RowKey = entity.RowKey;

        }

        public string PartitionKey { get; set; }
        public string RowKey { get; set; }


    }

Azure HTTP Trigger Function V2:

public static class FunctionReadFromTableStorage
    {
        [FunctionName("FunctionReadFromTableStorage")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            //Read Request Body
            var content = await new StreamReader(req.Body).ReadToEndAsync();

            //Extract Request Body and Parse To Class
            MyPoco objMyPoco = JsonConvert.DeserializeObject<MyPoco>(content);

            // Validate param because PartitionKey and RowKey is required to read from Table storage In this case , so I am checking here.
            dynamic validationMessage;

            if (string.IsNullOrEmpty(objMyPoco.PartitionKey))
            {
                validationMessage = new OkObjectResult("PartitionKey is required!");
                return (IActionResult)validationMessage;
            }
            if (string.IsNullOrEmpty(objMyPoco.RowKey))
            {
                validationMessage = new OkObjectResult("RowKey is required!");
                return (IActionResult)validationMessage;
            }


            // Table Storage operation  with credentials
            var client = new CloudTableClient(new Uri("https://YourStorageURL.table.core.windows.net/"),
                      new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials("YourStorageName", "xtaguZokAWbfYG4QDkBjT+YourStorageKey+T/kId/Ng+cl3TfYHtg=="));
            var table = client.GetTableReference("YourTableName");

            //Query filter
            var query = new TableQuery()
            {
                FilterString = string.Format("PartitionKey eq '{0}' and RowKey eq '{1}'", objMyPoco.PartitionKey, objMyPoco.RowKey)
            };


            //Request for storage query with query filter
            var continuationToken = new TableContinuationToken();
            var storageTableQueryResults = new List<TableStorageClass>();
            foreach (var entity in table.ExecuteQuerySegmentedAsync(query, continuationToken).GetAwaiter().GetResult().Results)
            {
                var request = new TableStorageClass(entity);
                storageTableQueryResults.Add(request);
            }

            //As we have to return IAction Type So converting to IAction Class Using OkObjectResult We Even Can Use OkResult
            var result = new OkObjectResult(storageTableQueryResults);
            return (IActionResult)result;
        }
    }

Point To Remember:

  1. In case of Azure Portal execution just get rid of FunctionReadFromTableStorage class
  2. You Need following reference to execute above code
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.WindowsAzure.Storage.Table;
using System.Collections.Generic;

Post Man Request Pattern:

Function Invoke Sample:

{
   "PartitionKey": "Your Param According to Table Storage Design" ,
   "RowKey": "Your Param According to Table Storage Design",
   "Directory": "Your Param According to Table Storage Design"
}

See The Screen Shot:

enter image description here

Post Man Response:

Response is subject to my own table design

[
    {
        "partitionKey": "Microsoft SharePoint Server",
        "rowKey": "2016"
    }
]

See The Screen Shot Below:

enter image description here

Note: I like to write code in simple and readable way. I just tried it for your case. If it resolved your issue my effort would be success then. This is the easiest way I know so far to read from azure table storage.

If you still have any question feel free to share. Thanks and happy coding!

1
votes

This inserts the request body to Table storage by binding to CloudTable

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage.Table;

namespace AzureFunctionsSandbox
{
    public class MyPoco : TableEntity
    {
        public string Body { get; set; }
    }

    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            [Table("Sandbox", "StorageConnectionString")] CloudTable table,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

            var poco = new MyPoco { PartitionKey = "HttpTrigger", RowKey = Guid.NewGuid().ToString(), Body = requestBody };

            var insertOperation = TableOperation.Insert(poco);

            await table.ExecuteAsync(insertOperation);

            return new OkObjectResult($"PK={poco.PartitionKey}, RK={poco.RowKey}, Text={poco.Body}");
        }
    }
}

Note: MyPoco inherits from TableEntity which allows you to create the TableOperation.Insert(poco) as .Insert() takes an ITableEntity.

local.settings.json

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",

    "StorageConnectionString": "UseDevelopmentStorage=true"
  }
}