Connect Azure Functions to Azure Storage using command line tools

In this article, you integrate an Azure Storage queue with the function and storage account you created in the previous quickstart article. You achieve this integration by using an output binding that writes data from an HTTP request to a message in the queue. Completing this article incurs no additional costs beyond the few USD cents of the previous quickstart. To learn more about bindings, see Azure Functions triggers and bindings concepts.

Configure your local environment

Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.

Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.

Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.

Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.

Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.

Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.

Retrieve the Azure Storage connection string

Earlier, you created an Azure Storage account for use by the function app. The connection string for this account is stored securely in app settings in Azure. By downloading the setting into the local.settings.json file, you can use that connection write to a Storage queue in the same account when running the function locally.

  1. From the root of the project, run the following command, replacing <APP_NAME> with the name of your function app from the previous quickstart. This command will overwrite any existing values in the file.

    func azure functionapp fetch-app-settings <APP_NAME>
    
  2. Open local.settings.json and locate the value named AzureWebJobsStorage, which is the Storage account connection string. You use the name AzureWebJobsStorage and the connection string in other sections of this article.

Important

Because local.settings.json contains secrets downloaded from Azure, always exclude this file from source control. The .gitignore file created with a local functions project excludes the file by default.

Register binding extensions

With the exception of HTTP and timer triggers, bindings are implemented as extension packages. Run the following dotnet add package command in the Terminal window to add the Storage extension package to your project.

dotnet add package Microsoft.Azure.WebJobs.Extensions.Storage 

Now, you can add the storage output binding to your project.

Add an output binding definition to the function

Although a function can have only one trigger, it can have multiple input and output bindings, which let you connect to other Azure services and resources without writing custom integration code.

You declare these bindings in the function.json file in your function folder. From the previous quickstart, your function.json file in the HttpExample folder contains two bindings in the bindings collection:

"bindings": [
    {
        "authLevel": "function",
        "type": "httpTrigger",
        "direction": "in",
        "name": "req",
        "methods": [
            "get",
            "post"
        ]
    },
    {
        "type": "http",
        "direction": "out",
        "name": "res"
    }
]
"scriptFile": "__init__.py",
"bindings": [
    {
        "authLevel": "function",
        "type": "httpTrigger",
        "direction": "in",
        "name": "req",
        "methods": [
            "get",
            "post"
        ]
    },
    {
        "type": "http",
        "direction": "out",
        "name": "$return"
    }
"bindings": [
  {
    "authLevel": "function",
    "type": "httpTrigger",
    "direction": "in",
    "name": "Request",
    "methods": [
      "get",
      "post"
    ]
  },
  {
    "type": "http",
    "direction": "out",
    "name": "Response"
  }
]

Each binding has at least a type, a direction, and a name. In the example above, the first binding is of type httpTrigger with the direction in. For the in direction, name specifies the name of an input parameter that's sent to the function when invoked by the trigger.

The second binding in the collection is named res. This http binding is an output binding (out) that is used to write the HTTP response.

To write to an Azure Storage queue from this function, add an out binding of type queue with the name msg, as shown in the code below:

    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "msg",
      "queueName": "outqueue",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

The second binding in the collection is of type http with the direction out, in which case the special name of $return indicates that this binding uses the function's return value rather than providing an input parameter.

To write to an Azure Storage queue from this function, add an out binding of type queue with the name msg, as shown in the code below:

"bindings": [
  {
    "authLevel": "anonymous",
    "type": "httpTrigger",
    "direction": "in",
    "name": "req",
    "methods": [
      "get",
      "post"
    ]
  },
  {
    "type": "http",
    "direction": "out",
    "name": "$return"
  },
  {
    "type": "queue",
    "direction": "out",
    "name": "msg",
    "queueName": "outqueue",
    "connection": "AzureWebJobsStorage"
  }
]

The second binding in the collection is named res. This http binding is an output binding (out) that is used to write the HTTP response.

To write to an Azure Storage queue from this function, add an out binding of type queue with the name msg, as shown in the code below:

    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "Request",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "Response"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "msg",
      "queueName": "outqueue",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

In this case, msg is given to the function as an output argument. For a queue type, you must also specify the name of the queue in queueName and provide the name of the Azure Storage connection (from local.settings.json) in connection.

In a C# project, the bindings are defined as binding attributes on the function method. Specific definitions depend on whether your app runs in-process (C# class library) or in an isolated process.

Open the HttpExample.cs project file and add the following parameter to the Run method definition:

[Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg,

The msg parameter is an ICollector<T> type, which represents a collection of messages that are written to an output binding when the function completes. In this case, the output is a storage queue named outqueue. The connection string for the Storage account is set by the StorageAccountAttribute. This attribute indicates the setting that contains the Storage account connection string and can be applied at the class, method, or parameter level. In this case, you could omit StorageAccountAttribute because you are already using the default storage account.

The Run method definition should now look like the following:

[FunctionName("HttpExample")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, 
    [Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg, 
    ILogger log)

In a Java project, the bindings are defined as binding annotations on the function method. The function.json file is then autogenerated based on these annotations.

Browse to the location of your function code under src/main/java, open the Function.java project file, and add the following parameter to the run method definition:

@QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage") OutputBinding<String> msg

The msg parameter is an OutputBinding<T> type, which represents a collection of strings that are written as messages to an output binding when the function completes. In this case, the output is a storage queue named outqueue. The connection string for the Storage account is set by the connection method. Rather than the connection string itself, you pass the application setting that contains the Storage account connection string.

The run method definition should now look like the following example:

@FunctionName("HttpTrigger-Java")
public HttpResponseMessage run(
        @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION)  
        HttpRequestMessage<Optional<String>> request, 
        @QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage") 
        OutputBinding<String> msg, final ExecutionContext context) {
    ...
}

For more information on the details of bindings, see Azure Functions triggers and bindings concepts and queue output configuration.

Add code to use the output binding

With the queue binding defined, you can now update your function to receive the msg output parameter and write messages to the queue.

Update HttpExample\__init__.py to match the following code, adding the msg parameter to the function definition and msg.set(name) under the if name: statement.

import logging

import azure.functions as func


def main(req: func.HttpRequest, msg: func.Out[func.QueueMessage]) -> str:

    name = req.params.get('name')
    if not name:
        try:
            req_body = req.get_json()
        except ValueError:
            pass
        else:
            name = req_body.get('name')

    if name:
        msg.set(name)
        return func.HttpResponse(f"Hello {name}!")
    else:
        return func.HttpResponse(
            "Please pass a name on the query string or in the request body",
            status_code=400
        )

The msg parameter is an instance of the azure.functions.Out class. Its set method writes a string message to the queue, in this case the name passed to the function in the URL query string.

Add code that uses the msg output binding object on context.bindings to create a queue message. Add this code before the context.res statement.

// Add a message to the Storage queue,
// which is the name passed to the function.
context.bindings.msg = (req.query.name || req.body.name);

At this point, your function should look as follows:

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    if (req.query.name || (req.body && req.body.name)) {
        // Add a message to the Storage queue,
        // which is the name passed to the function.
        context.bindings.msg = (req.query.name || req.body.name);
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: "Hello " + (req.query.name || req.body.name)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
};

Add code that uses the msg output binding object on context.bindings to create a queue message. Add this code before the context.res statement.

context.bindings.msg = name;

At this point, your function should look as follows:

import { AzureFunction, Context, HttpRequest } from "@azure/functions"

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    context.log('HTTP trigger function processed a request.');
    const name = (req.query.name || (req.body && req.body.name));

    if (name) {
        // Add a message to the storage queue, 
        // which is the name passed to the function.
        context.bindings.msg = name; 
        // Send a "hello" response.
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: "Hello " + (req.query.name || req.body.name)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
};

export default httpTrigger;

Add code that uses the Push-OutputBinding cmdlet to write text to the queue using the msg output binding. Add this code before you set the OK status in the if statement.

$outputMsg = $name
Push-OutputBinding -name msg -Value $outputMsg

At this point, your function should look as follows:

using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."

# Interact with query parameters or the body of the request.
$name = $Request.Query.Name
if (-not $name) {
    $name = $Request.Body.Name
}

if ($name) {
    # Write the $name value to the queue, 
    # which is the name passed to the function.
    $outputMsg = $name
    Push-OutputBinding -name msg -Value $outputMsg

    $status = [HttpStatusCode]::OK
    $body = "Hello $name"
}
else {
    $status = [HttpStatusCode]::BadRequest
    $body = "Please pass a name on the query string or in the request body."
}

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = $status
    Body = $body
})

Add code that uses the msg output binding object to create a queue message. Add this code before the method returns.

if (!string.IsNullOrEmpty(name))
{
    // Add a message to the output collection.
    msg.Add(string.Format("Name passed to the function: {0}", name));
}

At this point, your function should look as follows:

[FunctionName("HttpExample")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, 
    [Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg, 
    ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

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

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

    if (!string.IsNullOrEmpty(name))
    {
        // Add a message to the output collection.
        msg.Add(string.Format("Name passed to the function: {0}", name));
    }
    return name != null
        ? (ActionResult)new OkObjectResult($"Hello, {name}")
        : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}

Now, you can use the new msg parameter to write to the output binding from your function code. Add the following line of code before the success response to add the value of name to the msg output binding.

msg.setValue(name);

When you use an output binding, you don't have to use the Azure Storage SDK code for authentication, getting a queue reference, or writing data. The Functions runtime and queue output binding do those tasks for you.

Your run method should now look like the following example:

public HttpResponseMessage run(
        @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) 
        HttpRequestMessage<Optional<String>> request, 
        @QueueOutput(name = "msg", queueName = "outqueue", 
        connection = "AzureWebJobsStorage") OutputBinding<String> msg, 
        final ExecutionContext context) {
    context.getLogger().info("Java HTTP trigger processed a request.");

    // Parse query parameter
    String query = request.getQueryParameters().get("name");
    String name = request.getBody().orElse(query);

    if (name == null) {
        return request.createResponseBuilder(HttpStatus.BAD_REQUEST)
        .body("Please pass a name on the query string or in the request body").build();
    } else {
        // Write the name to the message queue. 
        msg.setValue(name);

        return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build();
    }
}

Update the tests

Because the archetype also creates a set of tests, you need to update these tests to handle the new msg parameter in the run method signature.

Browse to the location of your test code under src/test/java, open the Function.java project file, and replace the line of code under //Invoke with the following code.

@SuppressWarnings("unchecked")
final OutputBinding<String> msg = (OutputBinding<String>)mock(OutputBinding.class);
final HttpResponseMessage ret = new Function().run(req, msg, context);

Observe that you don't need to write any code for authentication, getting a queue reference, or writing data. All these integration tasks are conveniently handled in the Azure Functions runtime and queue output binding.

Run the function locally

  1. Run your function by starting the local Azure Functions runtime host from the LocalFunctionProj folder:

    func start
    

    Toward the end of the output, the following lines should appear:

     ...
    
     Now listening on: http://0.0.0.0:7071
     Application started. Press Ctrl+C to shut down.
    
     Http Functions:
    
             HttpExample: [GET,POST] http://localhost:7071/api/HttpExample
     ...
    
     

    Note

    If HttpExample doesn't appear as shown above, you likely started the host from outside the root folder of the project. In that case, use Ctrl+C to stop the host, navigate to the project's root folder, and run the previous command again.

  2. Copy the URL of your HttpExample function from this output to a browser and append the query string ?name=<YOUR_NAME>, making the full URL like http://localhost:7071/api/HttpExample?name=Functions. The browser should display a response message that echoes back your query string value. The terminal in which you started your project also shows log output as you make requests.

  3. When you're done, use Ctrl+C and choose y to stop the functions host.

Tip

During startup, the host downloads and installs the Storage binding extension and other Microsoft binding extensions. This installation happens because binding extensions are enabled by default in the host.json file with the following properties:

{
    "version": "2.0",
    "extensionBundle": {
        "id": "Microsoft.Azure.Functions.ExtensionBundle",
        "version": "[1.*, 2.0.0)"
    }
}

If you encounter any errors related to binding extensions, check that the above properties are present in host.json.

View the message in the Azure Storage queue

You can view the queue in the Azure portal or in the Microsoft Azure Storage Explorer. You can also view the queue in the Azure CLI, as described in the following steps:

  1. Open the function project's local.setting.json file and copy the connection string value. In a terminal or command window, run the following command to create an environment variable named AZURE_STORAGE_CONNECTION_STRING, pasting your specific connection string in place of <MY_CONNECTION_STRING>. (This environment variable means you don't need to supply the connection string to each subsequent command using the --connection-string argument.)

    export AZURE_STORAGE_CONNECTION_STRING="<MY_CONNECTION_STRING>"
    
  2. (Optional) Use the az storage queue list command to view the Storage queues in your account. The output from this command should include a queue named outqueue, which was created when the function wrote its first message to that queue.

    az storage queue list --output tsv
    
  3. Use the az storage message get command to read the message from this queue, which should be the first name you used when testing the function earlier. The command reads and removes the first message from the queue.

    echo `echo $(az storage message get --queue-name outqueue -o tsv --query '[].{Message:content}') | base64 --decode`
    

    Because the message body is stored base64 encoded, the message must be decoded before it's displayed. After you execute az storage message get, the message is removed from the queue. If there was only one message in outqueue, you won't retrieve a message when you run this command a second time and instead get an error.

Redeploy the project to Azure

Now that you've verified locally that the function wrote a message to the Azure Storage queue, you can redeploy your project to update the endpoint running on Azure.

In the LocalFunctionsProj folder, use the func azure functionapp publish command to redeploy the project, replacing<APP_NAME> with the name of your app.

func azure functionapp publish <APP_NAME>

In the local project folder, use the following Maven command to republish your project:

mvn azure-functions:deploy

Verify in Azure

  1. As in the previous quickstart, use a browser or CURL to test the redeployed function.

    Copy the complete Invoke URL shown in the output of the publish command into a browser address bar, appending the query parameter &name=Functions. The browser should display similar output as when you ran the function locally.

    The output of the function runs on Azure in a browser

  2. Examine the Storage queue again, as described in the previous section, to verify that it contains the new message written to the queue.

Clean up resources

After you've finished, use the following command to delete the resource group and all its contained resources to avoid incurring further costs.

az group delete --name AzureFunctionsQuickstart-rg

Next steps

You've updated your HTTP triggered function to write data to a Storage queue. Now you can learn more about developing Functions from the command line using Core Tools and Azure CLI: