结合使用 Azure Functions 和 SharePoint Webhook

Azure Functions 提供了一种托管 SharePoint Webhook 的简单方法:需通过浏览器添加 Webhook C# 或 JavaScript 代码,然后 Azure 处理函数的托管和扩展。 本指南介绍如何为 Webhook 设置和使用 Azure Functions。

创建 Azure 函数应用

需要执行的第一步是创建 Azure 函数应用,这是一种专注于托管Azure Functions的特殊 Azure Web 应用。

  1. 导航到https://portal.azure.com,搜索函数应用。 从搜索结果中选择函数应用

    创建 Azure 函数应用

  2. 单击添加选项。

    创建 Azure 函数应用

  3. 完成创建 Function App 所需的信息,然后单击查看 + 创建

    填写 Azure Function App 详细信息

  4. 单击创建

    Azure 函数应用确认页

  5. 部署完成后,单击转到资源

    Azure 函数应用已完成页

创建 Azure 函数

托管函数的应用就绪后,可单击新建函数链接继续创建你的第一个 Azure 函数。

Azure 函数应用登陆页

这样可以通过模板开始创建函数;如果使用 SharePoint Webhook,则需要 HTTP 触发的函数,由于要在示例中编写 C# 代码,因此要使用 HttpTrigger-CSharp 函数模板。

  1. 选择门户内开发环境选项,然后单击继续

    选择开发环境页面

  2. 选择Webhook + API触发器类型,然后单击创建

    选择触发器类型页面

    结果是用 C# 编写的“默认”Azure 函数。

    显示默认 C# 代码的

在我们的示例中,需要此 Azure 函数作为 SharePoint Webhook 服务运行,因此需要用 C# 实现以下内容:

  • 如果指定为调用的 URL 参数,将返回validationtoken。 必须按创建新订阅所述实现此操作,SharePoint 要求在 5 秒内回复。
  • 处理 JSON Webhook 通知。 在下面的示例中,我们已经选择将 JSON 存储于存储队列中,以便 Azure Web 作业可通过异步方式获得和处理它。
  • 根据需要,还可在 Webhook 服务中直接处理通知,但请注意,所有 Webhook 服务调用均需在 5 秒内完成,因此建议使用异步模型。

可通过将默认代码替换为下面的代码实现此操作:

#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public static async Task<IActionResult> Run(HttpRequest req,
  ICollector<string> outputQueueItem, ILogger log)
{
  log.LogInformation($"Webhook was triggered!");

  // Grab the validationToken URL parameter
  string validationToken = req.Query["validationtoken"];

  // If a validation token is present, we need to respond within 5 seconds by
  // returning the given validation token. This only happens when a new
  // webhook is being added
  if (validationToken != null)
  {
    log.LogInformation($"Validation token {validationToken} received");
    return (ActionResult)new OkObjectResult(validationToken);
  }

  log.LogInformation($"SharePoint triggered our webhook...great :-)");
  var content = await new StreamReader(req.Body).ReadToEndAsync();
  log.LogInformation($"Received following payload: {content}");

  var notifications = JsonConvert.DeserializeObject<ResponseModel<NotificationModel>>(content).Value;
  log.LogInformation($"Found {notifications.Count} notifications");

  if (notifications.Count > 0)
  {
    log.LogInformation($"Processing notifications...");
    foreach(var notification in notifications)
    {
      // add message to the queue
      string message = JsonConvert.SerializeObject(notification);
      log.LogInformation($"Before adding a message to the queue. Message content: {message}");
      outputQueueItem.Add(message);
      log.LogInformation($"Message added :-)");
    }
  }

  // if we get here we assume the request was well received
  return (ActionResult)new OkObjectResult($"Added to queue");
}

// supporting classes
public class ResponseModel<T>
{
  [JsonProperty(PropertyName = "value")]
  public List<T> Value { get; set; }
}

public class NotificationModel
{
  [JsonProperty(PropertyName = "subscriptionId")]
  public string SubscriptionId { get; set; }

  [JsonProperty(PropertyName = "clientState")]
  public string ClientState { get; set; }

  [JsonProperty(PropertyName = "expirationDateTime")]
  public DateTime ExpirationDateTime { get; set; }

  [JsonProperty(PropertyName = "resource")]
  public string Resource { get; set; }

  [JsonProperty(PropertyName = "tenantId")]
  public string TenantId { get; set; }

  [JsonProperty(PropertyName = "siteUrl")]
  public string SiteUrl { get; set; }

  [JsonProperty(PropertyName = "webId")]
  public string WebId { get; set; }
}

public class SubscriptionModel
{
  [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
  public string Id { get; set; }

  [JsonProperty(PropertyName = "clientState", NullValueHandling = NullValueHandling.Ignore)]
  public string ClientState { get; set; }

  [JsonProperty(PropertyName = "expirationDateTime")]
  public DateTime ExpirationDateTime { get; set; }

  [JsonProperty(PropertyName = "notificationUrl")]
  public string NotificationUrl {get;set;}

  [JsonProperty(PropertyName = "resource", NullValueHandling = NullValueHandling.Ignore)]
  public string Resource { get; set; }
}

配置 Azure 函数

我们选择了开始入手的正确模板,因此我们的配置几乎已完成。 你仍然需要执行的唯一操作是将 Azure 队列存储配置为输出绑定,以便我们可以在消息传入时将消息添加到队列中。

  1. 选择集成 ,然后新金输出添加输出绑定。

    Azure 函数集成设置

  2. 选择 Azure 队列存储 作为绑定类型,然后单击选择

    Azure 函数绑定选择

  3. 单击“保存”

    Azure 函数Azure 队列存储设置

测试 Azure 函数(验证令牌测试)

现在已设置完毕,可以进行首个 Azure 函数测试。

  1. 单击导航面板中 HttpTrigger1 函数的名称,导航回代码屏幕。 然后单击测试选项卡,打开右侧的测试面板。

    导航到 Azure 函数测试面板

  2. 使用随机字符串作为值添加 URL 参数validationtoken

使用此设置,可模拟验证新的 Webhook 添加时,SharePoint 调用 Webhook 服务的行为。

单击运行以测试...

设置 Azure 函数验证令牌测试

如果测试一切正常运行,在“日志”部分中将会看到服务已获调用,并通过 HTTP 200 响应返回传递的值。

validationToken 测试结果

测试 Azure 函数(SharePoint 列表事件测试)

现在进行第二次测试。 这将测试函数,就好像它是由 SharePoint 列表事件调用的一样。

  1. 在“测试”面板中,清除validationtoken URL 参数,并将请求正文替换为以下 JSON 对象。 然后单击 "运行"
{
  "value": [{
    "subscriptionId":"1111111111-3ef7-4917-ada1-xxxxxxxxxxxxx",
    "clientState":null,
    "expirationDateTime":"2020-06-14T16:22:51.2160000Z","resource":"xxxxxx-c0ba-4063-a078-xxxxxxxxx","tenantId":"4e2a1952-1ed1-4da3-85a6-xxxxxxxxxx",
    "siteUrl":"/sites/webhooktest",
    "webId":"xxxxx-3a7c-417b-964e-39f421c55d59"
  }]
}

如果一切正常,应会看到所有日志消息,包括指示消息已添加到队列的消息。

Webhook 通知测试结果

获取要在实现中使用的 Webhook URL

需要让 SharePoint 知晓我们使用的 Webhook URL。 为此,首先复制 Azure 函数 URL。

  1. 单击获取函数 URL

    日志消息控制台

  2. 单击复制将 Azure 函数应用 URL 复制到剪贴板。

    获取函数 URL 链接

在我们的示例中,以下是要使用的 Webhook URL:

https://fa-acto-spwebhook-dev-westus.azurewebsites.net/api/HttpTrigger1?code=LTFbMHrbVVQkhbL2xUplGRmY5XnAI9q/E4s5jvfeIh00KsF9Y7QsJw==

另请参阅