none
How to properly read data from a table storage binding? RRS feed

  • Question

  • I have a Node function that receives get requests from an internal api, does some attribute assignment, and spits back a JSON response. Initially, I set it up with a bunch of hard-coded constants, but we decided to move those values over to a storage table, so that we don't have to change the values in the function itself, if we need to raise our prices in the future. I followed the docs for creating a storage table, populated it, and configured my function to bind to it. As far as I can tell, I'm calling it correctly, but I'm getting a "TypeError: cannot read property x". Here's what my function.js looks like:

    {
      "bindings": [
        {
          "authLevel": "function",
          "type": "httpTrigger",
          "direction": "in",
          "name": "req",
          "methods": [
            "GET"
          ]
        },
        {
          "type": "http",
          "direction": "out",
          "name": "res"
        },
        {
          "type": "table",
          "name": "rate",
          "tableName": "Rates",
          "partitionKey": "Production",
          "rowKey": "Standard",
          "connection": "{TABLE_CONNECTION_STRING}",
          "direction": "in"
        }
      ],
      "disabled": false
    }

    And my index.js looks like this:

    module.exports = function (context, req) {
        context.log('Processing mailing');
    
        if (req.query.number_of_pages && req.query.delivery_country) {
            const first_page_special_domestic = context.bindings.rate.first_page_special_domestic;
            const addtl_page_special_domestic = context.bindings.rate.addtl_page_special_domestic;
            const express_domestic = context.bindings.rate.express_domestic;
            const certified = context.bindings.rate.certified;
            const tracking = context.bindings.rate.tracking;
            const registered = context.bindings.rate.registered;
            const return_envelope = context.bindings.rate.return_envelope;
            const first_page_special_international = context.bindings.rate.first_page_special_international;
            const addtl_page_special_international = context.bindings.rate.addtl_page_special_international;
            const first_page_international = context.bindings.rate.first_page_international;
            const addtl_page_international = context.bindings.rate.addtl_page_international;
            const express_international_flat_rate = context.bindings.rate.express_international_flat_rate;
    
            var number_of_pages = req.query.number_of_pages;
            var delivery_country = req.query.delivery_country;
            var flat_cost = 0.0;
            var per_page_cost = 0.0;
            var cost = 0.0;
            var tax = 0.0;
            var discount = 0.0;
            var first_page_domestic = context.bindings.rate.first_page_domestic;
            var addtl_page_domestic = context.bindings.rate.addtl_page_domestic;
    
            if (req.query.rate == 1) {
                first_page_domestic = context.bindings.rate.first_page_domestic_discount_one;
                addtl_page_domestic = context.bindings.rate.addtl_page_domestic_discount_one;
            }
            else if (req.query.rate == 2) {
                first_page_domestic = context.bindings.rate.first_page_domestic_discount_two;
                addtl_page_domestic = context.bindings.rate.addtl_page_domestic_discount_two;
            }
    
            if (delivery_country == "US") {
                if (req.query.special_paper) {
                    flat_cost = first_page_special_domestic;
                    per_page_cost = addtl_page_special_domestic;
                }
                else {
                    flat_cost = first_page_domestic;
                    per_page_cost = addtl_page_domestic;
                }
                if (req.query.tracking) flat_cost += tracking;
                if (req.query.certified) flat_cost += certified;
                if (req.query.registered) flat_cost += registered;
                if (req.query.express) flat_cost += express_domestic;
                if (req.query.return_envelope) flat_cost += return_envelope;
            }
            else {
                if (req.query.special_paper) {
                    flat_cost = first_page_special_international;
                    per_page_cost = addtl_page_special_international;
                }
                else {
                    flat_cost = first_page_international;
                    per_page_cost = addtl_page_international;
                }
                if (req.query.express) flat_cost += express_international_flat_rate;
                if (req.query.return_envelope) flat_cost += return_envelope;
            }
    
            if (number_of_pages > 1) {
                cost = ((number_of_pages - 1) * per_page_cost) + flat_cost;
            }
            else {
                cost = flat_cost;
            }
    
            cost = cost.toFixed(2);
    
            if (req.query.state_tax == "true" && delivery_country == "US") {
                tax = 0.095;
            }
    
            context.res = {
                status: 200,
                body: { "cost": cost, 
                        "tax": tax 
                }
            };
        }
        else {
            context.res = {
                status: 421,
                body: "Unprocessable Entity"
            };
        }
        context.done();
    };

    The error message is referring to the first const declared, first_page_special_domestic. My sense is that I've configured it incorrectly in some way, in my function.js. Any help would be appreciated!

    Friday, April 21, 2017 9:54 PM

Answers

  • The easiest way to do this is through the portal UI. You go into the integrate tab, then for the connection click "new":

    When you click this you get this blade that can pick your storage account:

    Select the storage account that has your table and then the UI will create an app setting and then update your function.json to use it. Thats all you should need to do. 

    • Marked as answer by PaulMulligan Tuesday, April 25, 2017 6:34 PM
    Tuesday, April 25, 2017 6:21 PM

All replies

  • For this part:

     "connection": "{TABLE_CONNECTION_STRING}",

    Is that your actual binding value or did you insert the curlies here as a placeholder? The reason I ask is because I expected it to look something like this:

     "connection": "TABLE_CONNECTION_STRING",

    Where your app settings has a key called TABLE_CONNECTION STRING with a value which is the connection string.

    Friday, April 21, 2017 11:57 PM
  • The curlies are a placeholder, I'm using the connection string listed on the function itself in the portal.
    Saturday, April 22, 2017 10:21 PM
  • I tried to reproduce your problem with no success. I suggest you do some more logging to try to figure out how your code/config differs from mine. Here are the details:

    function.json:

    {
      "bindings": [
        {
          "authLevel": "anonymous",
          "type": "httpTrigger",
          "direction": "in",
          "name": "req"
        },
        {
          "type": "http",
          "direction": "out",
          "name": "res"
        },
        {
          "type": "table",
          "name": "environmentSettings",
          "tableName": "Testing",
          "partitionKey": "Production",
          "rowKey": "Default",
          "take": 50,
          "connection": "AzureWebJobsDashboard",
          "direction": "in"
        }
      ],
      "disabled": false
    }

    index.js:

    module.exports = function (context, req) {
        context.log(context.bindings.environmentSettings);
    
        const lowercaseValue = context.bindings.environmentSettings.lowercase;
        const uppercaseValue = context.bindings.environmentSettings.UPPERCASE;
        const underscoreValue = context.bindings.environmentSettings.With_Under_Scores;
        
        context.log(lowercaseValue);
        context.log(uppercaseValue);
        context.log(underscoreValue);
    
        context.done();
    };

    output:

    2017-04-25T01:59:12.453 Function started (Id=572f4625-db8a-48ad-afc1-490217c0fd8b)
    2017-04-25T01:59:12.485 { UPPERCASE: 'SOME UPPERCASE VALUE',
      With_Under_Scores: 'lots_of_underscores_here',
      lowercase: 'some lowercase value',
      PartitionKey: 'Production',
      RowKey: 'Default' }
    2017-04-25T01:59:12.485 some lowercase value
    2017-04-25T01:59:12.485 SOME UPPERCASE VALUE
    2017-04-25T01:59:12.485 lots_of_underscores_here
    2017-04-25T01:59:12.485 Function completed (Success, Id=572f4625-db8a-48ad-afc1-490217c0fd8b, Duration=25ms)
    

    The record I'm using in table storage:

    Tuesday, April 25, 2017 2:02 AM
  • Okay, I see where my problem is. I didn't realize that AzureWebJobsStorage was an environment variable. I think I just need to set that properly, but the format is a bit confusing. Assume my storage endpoint is "https://mytable.table.core.windows.net/Rates". In the following connection string, where should I put this url?

    DefaultEndpointsProtocol=https;AccountName=mytablecabe84;AccountKey=myaccountkey

    Should I put it in accountname, or add a new property called name?

    Tuesday, April 25, 2017 5:52 PM
  • The easiest way to do this is through the portal UI. You go into the integrate tab, then for the connection click "new":

    When you click this you get this blade that can pick your storage account:

    Select the storage account that has your table and then the UI will create an app setting and then update your function.json to use it. Thats all you should need to do. 

    • Marked as answer by PaulMulligan Tuesday, April 25, 2017 6:34 PM
    Tuesday, April 25, 2017 6:21 PM