65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Azure Functions 的联系表单

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2019 年 7 月 16 日

CPOL

5分钟阅读

viewsIcon

8044

对于那些还没听过 Azure Functions 的人来说,它是一项服务,可以让你构建事件驱动的微服务,而无需管理任何基础设施。

背景

对于那些运行静态网站而没有 WebApp 服务器或虚拟机的人来说,这可以增加大量额外的功能,而这些功能通常需要专用基础设施来提供。另一件好事是 Azure Functions 的定价基于执行时间,与运行 WebApp 服务器相比非常便宜。

例如,拥有一个 jQuery 联系表单而不是发布电子邮件地址通常需要一个完整的 Web 应用程序来提交表单。与其将我的网站改回 Web 应用程序并放弃 CDN 交付的网站,不如让我们看看如何使用 Azure Functions 构建一个联系表单 API。

入门

需要注意的是,Microsoft 在 Azure 门户中提供了一些内置工具,允许你在门户中直接创建各种语言的函数,而无需任何编辑器。然而,在本文中,我将使用 Visual Studio Community Edition 中的 C#,因为我对该 IDE 更熟悉,并且它允许我在部署函数到 Azure 之前在本地进行测试和调试。

SendGrid

要跟随本文的内容,你还需要一个 SendGrid 账户和一个为你的函数生成的 API 密钥。你可以先使用 SendGrid API 的完全访问权限,然后在迁移到生产环境时将其限制下来。安装 Azure SDK 和 Azure 存储模拟器 也是有益的,这样你就可以在迁移到 Azure 资源之前在本地进行测试。

步骤 1 - 创建项目

Create Project

首先,在 Visual Studio 中创建你的 Azure Functions 项目。请记住,Functions 应该以微服务的心态来构建,因此在规划 Functions 时,你在这里创建的项目应该包含一组函数。就我而言,我创建了一个函数项目来存放我网站所需的所有函数。

Project Details

创建新的函数项目时,你还需要选择要创建的版本(在此情况下为使用 .NET Core 的版本 2),以及函数使用的存储账户及其访问权限。目前,我们选择存储模拟器在本地进行测试,但有一个基于环境的变量在部署到 Azure 时会保存此设置。此时的触发器是为默认函数创建的触发器,在这个阶段并不重要。

Cleanup

项目创建完成后,你应该只有几个文件。如果你选择了 HTTP Trigger,请删除 Function1.cs 文件,因为它是默认函数,我们将从头开始创建一个。

步骤 2 - 创建函数

Create Function

我在我的项目中添加了一个名为“联系表单”的新文件夹,并添加了一个名为 ContactPostMessage.cs 的新函数。这个函数的作用是接收来自互联网的 POST 请求,其中包含特定的 JSON 负载,并将它们作为电子邮件发送。

Function Settings

这个函数应该运行在“Http trigger”下,并具有“Function”访问权限。这基本上意味着任何拥有 Function 访问密钥的人都可以使用 HTTP / HTTPS 将数据发送到这个 API,API 会将其转换为电子邮件。我们使用 Function 作为访问权限的原因是我们将使用 API Management 前端来包装所有 Http trigger Functions,并提供一些额外的功能。

步骤 3 - 编写函数代码

首先,我们创建一个 function.json 文件来提供函数输入和输出的 绑定。此文件必须与你的函数位于同一目录中,并且是 JSON 格式的绑定数组。此文件的代码如下所示:

{
  "bindings": [
    {
      "authLevel": "function",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in",
      "methods": [
        "post"
      ]
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    },
    {
      "name": "mail",
      "type": "sendGrid",
      "apiKey": "SENDGRID_API_KEY",
      "to": "CONTACT_TO_ADDRESS",
      "direction": "out"
    }
  ]
}

此文件包含一个输入绑定,类型为 httpTrigger,仅允许 post 请求,以及两个输出类型:一个 http 返回类型和一个 SendGrid 输出类型。现在,让我们修改函数以接受、检查和处理一个 JSON 文件,其中包含人们输入以发送消息的信息。让我们逐步分析 ContactPostMessage 类,以展示我们在此函数中所做的工作:

public static class ContactPostMessage
{               
    [FunctionName("ContactPostMessage")]
    public static async Task<IActionResult> Run( 
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
        [SendGrid(ApiKey = "AzureWebJobsSendGridApiKey")] 
                   IAsyncCollector<SendGridMessage> messageCollector,
        ILogger log)
    {

前几行定义了类和 run 函数,该函数在函数被触发时被调用。这与我们在 function.json 文件中的内容相匹配。首先,我们处理一个 HttpTrigger,并将请求捕获到 req 变量中。我们还将在 messageCollector 变量中注入一个 SendGrid 消息收集器,并将日志系统注入到 log 变量中。

        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        string name, email, subject, message = "";
        try
        {
            dynamic data = JsonConvert.DeserializeObject(requestBody);

            name = data?.name;
            email = data?.email;
            subject = data?.subject;
            message = data?.message;
        }
        catch(Exception ex)
        {
            log.LogInformation("Error trying to transform JSON payload - " + ex.ToString());
            return new BadRequestObjectResult("Malformed JSON payload");
        }

在接下来的代码段中,我们读取请求的正文,并尝试解包一个包含联系表单数据的 JSON 对象。我们使用流读取器获取请求正文,然后使用 Newtonsoft.Json 库的 JsonConvert 类将正文解包为 JSON 对象。然后,我们将所需数据放入一些 string 变量中。我们这样做是为了能够将任何错误发送回去,如果消息正文不符合我们的基本要求,我们就可以通过 try-catch 方法来处理。

        if (Regex.IsMatch(name, @"^[A-Za-z]{3,}$"))
            return new BadRequestObjectResult("Name may only contain alpha-numeric characters");

        if (Regex.IsMatch(subject, @"^[<>%\$!#^&*+=|/`~]$"))
            return new BadRequestObjectResult("Subject may not contain special characters");

        if (Regex.IsMatch(message, @"^[<>%\$!#^&*+=|/`~]$"))
            return new BadRequestObjectResult("Message may not contain special characters");
        try
        {
            var fromAddr = new System.Net.Mail.MailAddress(email);
            if (fromAddr.Address != email)
                return new BadRequestObjectResult("E-Mail address is not valid");
        }
        catch
        {
            return new BadRequestObjectResult("E-Mail address is not valid");
        }

完成基本检查后,我们使用 Regex 对数据元素本身进行更完整的检查,确保我们有一个有效的电子邮件地址、消息、主题和姓名。

        var mail = new SendGridMessage();
        mail.AddTo(Environment.GetEnvironmentVariable("CONTACT_TO_ADDRESS", 
                   EnvironmentVariableTarget.Process));
        mail.SetFrom(email, name);
        mail.SetSubject(subject);
        mail.AddContent("text/html", message);

        await messageCollector.AddAsync(mail);

        return new OkResult();
    }
}

最后,我们将 JSON 负载组合成一个 SendGrid 消息,并使用注入的收集器发送它。完成所有这些之后,将函数项目部署到 Azure,我使用 Azure DevOps 连接到我的存储库进行发布。为了使代码正常工作,你需要在已发布的资源(在平台功能区域的配置部分)中添加以下变量:

  • AzureWebJobsSendGridApiKey - 你的 SendGrid API 密钥
  • AzureWebJobsStorage - 要与此函数应用关联的存储账户
  • CONTACT_TO_ADDRESS - 发送所有生成电子邮件的收件地址

步骤 4 - 使用 API Management 展示函数

你可以直接发布一个函数,但我将其放在 API Management 网关后面,这样我就可以将任何其他函数打包成一组产品,它还可以让我指定高级策略,例如速率限制,以帮助防止人们对 API 进行垃圾邮件攻击。

Create from Function

API Management 有一个选项可以直接与 Azure 函数链接,它会在后台设置正确的访问机制。设置完成后,你可以通过单个前端发布多个函数。

摘要

以上就是创建 Azure Functions 的基础知识。它对于实现“发送即忘”的工作负载非常有用。你可以在 GitHub 项目 中找到此函数的示例代码。

历史

  • 2019 年 7 月 16 日:初始版本
© . All rights reserved.