使用 Visual Studio Code 将 Azure Functions 连接到 Azure 存储

无需编写自己的集成代码,即可使用 Azure Functions 将 Azure 服务和其他资源连接到函数。 这些绑定表示输入和输出,在函数定义中声明。 绑定中的数据作为参数提供给函数。 触发器是一种特殊类型的输入绑定。 尽管一个函数只有一个触发器,但它可以有多个输入和输出绑定。 有关详细信息,请参阅 Azure Functions 触发器和绑定的概念

本文介绍如何使用 Visual Studio Code 将 Azure 存储连接到在前一篇快速入门文章中创建的函数。 添加到此函数的输出绑定会将 HTTP 请求中的数据写入到 Azure 队列存储队列中的消息。

大多数绑定都需要一个存储的连接字符串,函数将使用该字符串来访问绑定的服务。 为便于操作,请使用连同函数应用一起创建的存储帐户。 与此帐户建立的连接已存储在名为 AzureWebJobsStorage 的应用设置中。

配置本地环境

在开始之前,必须满足以下要求:

本文假设你已从 Visual Studio Code 登录到 Azure 订阅。 你可以通过从命令面板运行 Azure: Sign In 进行登录。

下载函数应用设置

上一篇快速入门文章中,你已在 Azure 中创建了一个函数应用以及所需的存储帐户。 此帐户的连接字符串安全存储在 Azure 中的应用设置内。 在本文中,你要将消息写入到同一帐户中的存储队列。 若要在本地运行函数时连接到该存储帐户,必须将应用设置下载到 local.settings.json 文件。

  1. F1 键打开命令面板,然后搜索并运行命令 Azure Functions: Download Remote Settings....

  2. 选择你在前一篇文章中创建的函数应用。 选择“全是”覆盖现有本地设置。

    重要

    由于 local.settings.json 文件包含机密,因此请勿发布,应将其从源代码管理中排除。

  3. 复制值 AzureWebJobsStorage,这是存储帐户连接字符串值的键。 你将使用此连接来验证输出绑定是否按预期方式工作。

注册绑定扩展

由于你使用队列存储输出绑定,因此在运行项目之前,必须安装存储绑定扩展。

项目已配置为使用扩展捆绑包,因此会自动安装一组预定义的扩展包。

在项目根目录下的 host.json 文件中启用扩展捆绑包,如下所示:

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

现在,你可以将存储输出绑定添加到项目。

绑定(HTTP 和计时器触发器除外)将实现为扩展包。 在终端窗口中运行以下 dotnet add package 命令,将存储扩展包添加到项目中。

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

现在,你可以将存储输出绑定添加到项目。

添加输出绑定

在 Functions 中,每种类型的绑定都需要一个 directiontype,以及要在 function.json 文件中定义的唯一 name。 定义这些属性的方式取决于函数应用的语言。

直接在 function.json 文件中定义绑定属性。 根据绑定类型,可能还需要其他属性。 队列输出配置描述 Azure 存储队列绑定所需的字段。 扩展可以轻松地将绑定添加到 function.json 文件。

要创建绑定,请右键单击(在 macOS 系统上,请按住 Ctrl 并单击)HttpTrigger 文件夹中的 function.json 文件,并选择“添加绑定...”。请按照提示为新绑定定义以下绑定属性:

Prompt Value 说明
选择绑定方向 out 该绑定是输出绑定。
选择具有方向的绑定... Azure Queue Storage 该绑定是 Azure 存储队列绑定。
用于在代码中标识此绑定的名称 msg 用于标识代码中引用的绑定参数的名称。
要将消息发送到的队列 outqueue 绑定要写入到的队列的名称。 如果 queueName 不存在,首次使用绑定时,它会创建该属性。
从 local.setting.json 中选择设置 AzureWebJobsStorage 包含存储帐户连接字符串的应用程序设置的名称。 AzureWebJobsStorage 设置包含连同函数应用一起创建的存储帐户的连接字符串。

绑定将添加到 function.json 中的 bindings 数组,应如下所示:

    {
      "type": "queue",
      "direction": "out",
      "name": "msg",
      "queueName": "outqueue",
      "connection": "AzureWebJobsStorage"
    }

在 C# 项目中,绑定被定义为函数方法上的绑定属性。 具体定义取决于应用是在进程内(C# 类库)还是在隔离进程中运行。

打开 HttpExample.cs 项目文件,并将以下参数添加到 方法定义中:

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

msg 参数为一个 ICollector<T> 类型,表示函数完成时写入到输出绑定的消息集合。 在这种情况下,输出是名为的 outqueue 存储队列。 StorageAccountAttribute 设置存储帐户的连接字符串。 此属性指示包含存储帐户连接字符串的设置,可以在类、方法或参数级别应用。 在本例中,可以省略 StorageAccountAttribute,因为你已使用默认存储帐户。

Run 方法定义现在必须如以下代码所示:

        [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)

在 Java 项目中,绑定被定义为函数方法上的绑定注释。 然后根据这些注释自动生成 function.json 文件。

浏览到函数代码在 src/main/java 下的位置,打开 Function.java 项目文件,然后将以下参数添加到 方法定义:

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

msg 参数为 OutputBinding<T> 类型,表示函数完成时作为消息写入到输出绑定的字符串集合。 在这种情况下,输出是名为的 outqueue 存储队列。 存储帐户的连接字符串由 connection 方法设置。 请传递包含存储帐户连接字符串的应用程序设置,而不是传递连接字符串本身。

run 方法定义现在应如以下示例所示:

@FunctionName("HttpExample")
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) {

添加使用输出绑定的代码

定义绑定后,可以使用绑定的 name,将其作为函数签名中的属性进行访问。 使用输出绑定时,无需使用 Azure 存储 SDK 代码进行身份验证、获取队列引用或写入数据。 Functions 运行时和队列输出绑定将为你执行这些任务。

添加在 context.bindings 上使用 msg 输出绑定对象来创建队列消息的代码。 请在 context.res 语句之前添加此代码。

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

此时,你的函数一定如下所示:

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"
        };
    }
};

添加在 context.bindings 上使用 msg 输出绑定对象来创建队列消息的代码。 请在 context.res 语句之前添加此代码。

        context.bindings.msg = name; 

此时,你的函数一定如下所示:

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; 

添加使用 Push-OutputBinding cmdlet 通过 msg 输出绑定将文本写入队列的代码。 在 if 语句中设置“正常”状态之前,请添加此代码。

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

此时,你的函数一定如下所示:

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
})

按以下代码所示更新 HttpExample\__init__.py,将 msg 参数添加到函数定义,并将 msg.set(name) 添加到 if name: 语句下:

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
        )

msg 参数是 azure.functions.Out class 的实例。 set 方法将字符串消息写入队列。 在本例中,此消息是在 URL 查询字符串中传递给函数的名称。

添加使用 msg 输出绑定对象来创建队列消息的代码。 请在方法返回之前添加此代码。

            if (!string.IsNullOrEmpty(name))
            {
                // Add a message to the output collection.
                msg.Add(name);
            }

此时,你的函数一定如下所示:

        [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(name);
            }
            return name != null
                ? (ActionResult)new OkObjectResult($"Hello, {name}")
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }

现在可以使用新的 msg 参数,从函数代码写入到输出绑定。 将以下代码行添加到成功响应之前,以便将 name 的值添加到 msg 输出绑定。

msg.setValue(name);

使用输出绑定时,无需使用 Azure 存储 SDK 代码进行身份验证、获取队列引用或写入数据。 Functions 运行时和队列输出绑定将为你执行这些任务。

run 方法现在应如以下示例所示:

    @FunctionName("HttpExample")
    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();
        }
    }

更新测试

由于原型还创建一组测试,因此需更新这些测试,以便处理 run 方法签名中的新 msg 参数。

浏览到测试代码在 src/test/java 下的位置,打开 Function.java 项目文件,将 下的代码行替换为以下代码。

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

在本地运行函数

Visual Studio Code 与 Azure Functions Core Tools 相集成,便于你在发布到 Azure 之前在本地开发计算机上运行此项目。

  1. 若要调用函数,请按 F5 启动函数应用项目。 “终端”面板将显示 Core Tools 的输出。 应用将在“终端”面板中启动。 可以看到 HTTP 触发函数的 URL 终结点在本地运行。

    本地函数 Visual Studio Code 输出的屏幕截图。

    如果在 Windows 上运行时遇到问题,请确保用于 Visual Studio Code 的默认终端未设置为“WSL Bash”。

  2. 运行 Core Tools 后,转到“Azure: Functions”区域。 在“Functions”下,展开“本地项目”>“Functions” 。 右键单击 (Windows) 或按 Ctrl - 单击 (macOS) HttpExample 函数,然后选择“立即执行函数...”。

    Visual Studio Code 中的“立即执行函数”的屏幕截图。

  3. 在“输入请求正文”中,按 Enter 向函数发送请求消息。

  4. 当函数在本地执行并返回响应时,Visual Studio Code 中将引发通知。 函数执行的相关信息将显示在“终端”面板中。

  5. 按 Ctrl + C 停止 Core Tools 并断开调试器的连接

在本地运行函数

  1. 与上一篇文章中所述,按 F5 启动函数应用项目和 Core Tools。

  2. 运行 Core Tools 后,转到“Azure: Functions”区域。 在“Functions”下,展开“本地项目”>“Functions” 。 右键单击 HttpExample 函数(在 Mac 中按 Ctrl-单击),然后选择“立即执行函数...”。

    从 Visual Studio Code 执行函数的屏幕截图。

  3. 在“输入请求正文”中,你将看到请求消息正文值 { "name": "Azure" }。 按 Enter 将此请求消息发送给函数。

  4. 返回响应后,按 Ctrl + C 停用 Core Tools。

由于使用的是存储连接字符串,因此函数在本地运行时会连接到 Azure 存储帐户。 首次使用输出绑定时,Functions 运行时会在存储帐户中创建名为 outqueue 的新队列。 将使用存储资源管理器来验证队列是否与新消息一起创建。

将存储资源管理器连接到帐户

如果已安装 Azure 存储资源管理器并已将其连接到 Azure 帐户,请跳过此部分。

  1. 运行 Azure 存储资源管理器工具,选择左侧的连接图标,并选择“添加帐户”。

    屏幕截图显示如何将 Azure 帐户添加到 Azure 存储资源管理器。

  2. 在“连接”对话框中,依次选择“添加 Azure 帐户”、你的 Azure 环境和“登录...”。

    “登录到 Azure 帐户”窗口的屏幕截图。

成功登录到帐户后,将看到与你的帐户关联的所有 Azure 订阅。

检查输出队列

  1. 在 Visual Studio Code 中,按 F1 打开命令面板,然后搜索并运行命令 Azure Storage: Open in Storage Explorer,选择你的存储帐户名称。 随即将在 Azure 存储资源管理器中打开你的存储帐户。

  2. 展开“队列”节点,然后选择名为 outqueue 的队列。

    此队列包含在运行 HTTP 触发的函数时队列输出绑定创建的消息。 如果使用 Azure 的默认 name 值调用了此函数,则队列消息为“传递给函数的名称: Azure”。

    Azure 存储资源管理器中显示的队列消息的屏幕截图。

  3. 再次运行函数,发送另一个请求,此时会看到新消息出现在队列中。

现在,可将更新的函数应用重新发布到 Azure。

重新部署并验证更新的应用

  1. 在 Visual Studio Code 中,按 F1 打开命令面板。 在命令面板中,搜索并选择 Azure Functions: Deploy to function app...

  2. 选择你在第一篇文章中创建的函数应用。 由于你要将项目重新部署到同一个应用,因此请选择“部署”以关闭关于覆盖文件的警告。

  3. 部署完成后,可再次使用“立即执行函数...”功能在 Azure 中触发该函数。

  4. 再次查看存储队列中的消息,以验证输出绑定是否在队列中生成了新的消息。

清理资源

在 Azure 中,“资源”是指函数应用、函数、存储帐户等。 这些资源可以组合到资源组中,删除该组即可删除组中的所有内容。

你已创建完成这些快速入门所需的资源。 这些资源可能需要付费,具体取决于帐户状态服务定价。 如果不再需要这些资源,请参阅下面介绍的资源删除方法:

  1. 在 Visual Studio Code 中,按 F1 打开命令面板。 在命令面板中,搜索并选择 Azure: Open in portal

  2. 选择你的函数应用,然后按 Enter。 随即将在 Azure 门户中打开函数应用页面。

  3. 在“概览”选项卡中,选择“资源组”旁边的命名链接。

    从函数应用页选择要删除的资源组的屏幕截图。

  4. 在“资源组”页上查看所包括的资源的列表,然后验证这些资源是否是要删除的。

  5. 选择“删除资源组”,然后按说明操作。

    可能需要数分钟才能删除完毕。 完成后会显示一个通知,持续数秒。 也可以选择页面顶部的钟形图标来查看通知。

后续步骤

现已更新 HTTP 触发的函数,使其将数据写入存储队列。 现在,可以详细了解如何使用 Visual Studio Code 开发 Functions: