将事件接收到 HTTP 终结点Receive events to an HTTP endpoint

本文介绍如何验证 HTTP 终结点以接收来自事件订阅的事件并随后接收和反序列化事件。This article describes how to validate an HTTP endpoint to receive events from an Event Subscription and then receive and deserialize events. 本文使用 Azure 函数进行演示,但无论应用程序托管在何处,这些概念都适用。This article uses an Azure Function for demonstration purposes, however the same concepts apply regardless of where the application is hosted.

备注

强烈推荐在通过事件网格触发 Azure 函数时使用事件网格触发器It is strongly recommended that you use an Event Grid Trigger when triggering an Azure Function with Event Grid. 此处使用泛型 WebHook 触发器进行演示。The use of a generic WebHook trigger here is demonstrative.

先决条件Prerequisites

需要包含 HTTP 触发函数的函数应用。You need a function app with an HTTP triggered function.

添加依赖项Add dependencies

若要使用 .NET 进行开发,请向 Microsoft.Azure.EventGrid Nuget 包的函数添加依赖项If you're developing in .NET, add a dependency to your function for the Microsoft.Azure.EventGrid NuGet package. 本文中的示例需要使用版本 1.4.0 或更高版本。The examples in this article require version 1.4.0 or later.

可通过发布 SDKs 引用将 SDK 用于其他语言。SDKs for other languages are available via the Publish SDKs reference. 这些包中有用于本机事件类型(如 EventGridEventStorageBlobCreatedEventDataEventHubCaptureFileCreatedEventData)的模型。These packages have the models for native event types such as EventGridEvent, StorageBlobCreatedEventData, and EventHubCaptureFileCreatedEventData.

在 Azure 函数中单击“查看文件”链接(Azure Functions 门户中最右侧的窗格),然后创建一个名为 project.json 的文件。Click on the "View Files" link in your Azure Function (right most pane in the Azure functions portal), and create a file called project.json. 将以下代码添加到 project.json 文件并进行保存:Add the following contents to the project.json file and save it:

{
 "frameworks": {
   "net46":{
     "dependencies": {
       "Microsoft.Azure.EventGrid": "2.0.0"
     }
   }
  }
}

所添加的 NuGet 包

终结点验证Endpoint validation

首先需要处理 Microsoft.EventGrid.SubscriptionValidationEvent 事件。The first thing you want to do is handle Microsoft.EventGrid.SubscriptionValidationEvent events. 每次有人订阅某个事件时,事件网格都会在数据有效负载中通过 validationCode 向终结点发送一个验证事件。Every time someone subscribes to an event, Event Grid sends a validation event to the endpoint with a validationCode in the data payload. 终结点必须回显到响应正文中才能证明此终结点有效且被你所拥有The endpoint is required to echo this back in the response body to prove the endpoint is valid and owned by you. 如果你使用的是事件网格触发器(而不是 Webhook 触发函数),系统会为你执行终结点验证。If you're using an Event Grid Trigger rather than a WebHook triggered Function, endpoint validation is handled for you. 如果使用第三方 API 服务(例如 ZapierIFTTT),可能无法以编程方式回显验证码。If you use a third-party API service (like Zapier or IFTTT), you might not be able to programmatically echo the validation code. 对于这些服务,可以使用订阅验证事件中发送的验证 URL 手动验证订阅。For those services, you can manually validate the subscription by using a validation URL that is sent in the subscription validation event. validationUrl 属性中复制该 URL 并通过 REST 客户端或 Web 浏览器发送 GET 请求。Copy that URL in the validationUrl property and send a GET request either through a REST client or your web browser.

在 C# 中,DeserializeEventGridEvents() 函数会反序列化事件网格事件。In C#, the DeserializeEventGridEvents() function deserializes the Event Grid events. 它将事件数据反序列化为适当的类型(如 StorageBlobCreatedEventData)。It deserializes the event data into the appropriate type, such as StorageBlobCreatedEventData. Microsoft.Azure.EventGrid.EventTypes 类可用于获取受支持的事件类型和名称。Use the Microsoft.Azure.EventGrid.EventTypes class to get supported event types and names.

若要以编程方式回显验证码,请运行下面的代码。To programmatically echo the validation code, use the following code. 若要获取相关示例,可以访问事件网格使用者示例You can find related samples at Event Grid Consumer example.

using System.Net;
using Newtonsoft.Json;
using Microsoft.Azure.EventGrid.Models;
using Microsoft.Azure.EventGrid;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info($"C# HTTP trigger function begun");
    string response = string.Empty;

    string requestContent = await req.Content.ReadAsStringAsync();
    log.Info($"Received events: {requestContent}");

    EventGridSubscriber eventGridSubscriber = new EventGridSubscriber();

    EventGridEvent[] eventGridEvents = eventGridSubscriber.DeserializeEventGridEvents(requestContent);

    foreach (EventGridEvent eventGridEvent in eventGridEvents)
    {
        if (eventGridEvent.Data is SubscriptionValidationEventData)
        {
            var eventData = (SubscriptionValidationEventData)eventGridEvent.Data;
            log.Info($"Got SubscriptionValidation event data, validation code: {eventData.ValidationCode}, topic: {eventGridEvent.Topic}");
            // Do any additional validation (as required) and then return back the below response

            var responseData = new SubscriptionValidationResponse()
            {
                ValidationResponse = eventData.ValidationCode
            };

            return req.CreateResponse(HttpStatusCode.OK, responseData);
        }
    }

    return req.CreateResponse(HttpStatusCode.OK, response);
}
module.exports = function (context, req) {
    context.log('JavaScript HTTP trigger function begun');
    var validationEventType = "Microsoft.EventGrid.SubscriptionValidationEvent";

    for (var events in req.body) {
        var body = req.body[events];
        // Deserialize the event data into the appropriate type based on event type
        if (body.data && body.eventType == validationEventType) {
            context.log("Got SubscriptionValidation event data, validation code: " + body.data.validationCode + " topic: " + body.topic);

            // Do any additional validation (as required) and then return back the below response
            var code = body.data.validationCode;
            context.res = { status: 200, body: { "ValidationResponse": code } };
        }
    }
    context.done();
};

测试验证响应Test validation response

通过将样本事件粘贴到函数的测试字段,测试验证响应函数:Test the validation response function by pasting the sample event into the test field for the function:

[{
  "id": "2d1781af-3a4c-4d7c-bd0c-e34b19da4e66",
  "topic": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "subject": "",
  "data": {
    "validationCode": "512d38b6-c7b8-40c8-89fe-f46f9e9622b6"
  },
  "eventType": "Microsoft.EventGrid.SubscriptionValidationEvent",
  "eventTime": "2018-01-25T22:12:19.4556811Z",
  "metadataVersion": "1",
  "dataVersion": "1"
}]

单击“运行”时,输出应为“200 确定”且正文中显示 {"ValidationResponse":"512d38b6-c7b8-40c8-89fe-f46f9e9622b6"}When you click Run, the Output should be 200 OK and {"ValidationResponse":"512d38b6-c7b8-40c8-89fe-f46f9e9622b6"} in the body:

验证响应

处理 Blob 存储事件Handle Blob storage events

现在,让我们对该函数进行扩展来处理 Microsoft.Storage.BlobCreatedNow, let's extend the function to handle Microsoft.Storage.BlobCreated:

using System.Net;
using Newtonsoft.Json;
using Microsoft.Azure.EventGrid.Models;
using Microsoft.Azure.EventGrid;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info($"C# HTTP trigger function begun");
    string response = string.Empty;

    string requestContent = await req.Content.ReadAsStringAsync();
    log.Info($"Received events: {requestContent}");

    EventGridSubscriber eventGridSubscriber = new EventGridSubscriber();

    EventGridEvent[] eventGridEvents = eventGridSubscriber.DeserializeEventGridEvents(requestContent);

    foreach (EventGridEvent eventGridEvent in eventGridEvents)
    {
        if (eventGridEvent.Data is SubscriptionValidationEventData)
        {
            var eventData = (SubscriptionValidationEventData)eventGridEvent.Data;
            log.Info($"Got SubscriptionValidation event data, validation code: {eventData.ValidationCode}, topic: {eventGridEvent.Topic}");
            // Do any additional validation (as required) and then return back the below response

            var responseData = new SubscriptionValidationResponse()
            {
                ValidationResponse = eventData.ValidationCode
            };

            return req.CreateResponse(HttpStatusCode.OK, responseData);
        }
        else if (eventGridEvent.Data is StorageBlobCreatedEventData)
        {
            var eventData = (StorageBlobCreatedEventData)eventGridEvent.Data;
            log.Info($"Got BlobCreated event data, blob URI {eventData.Url}");
        }
    }

    return req.CreateResponse(HttpStatusCode.OK, response);
}
module.exports = function (context, req) {
    context.log('JavaScript HTTP trigger function begun');
    var validationEventType = "Microsoft.EventGrid.SubscriptionValidationEvent";
    var storageBlobCreatedEvent = "Microsoft.Storage.BlobCreated";

    for (var events in req.body) {
        var body = req.body[events];
        // Deserialize the event data into the appropriate type based on event type  
        if (body.data && body.eventType == validationEventType) {
            context.log("Got SubscriptionValidation event data, validation code: " + body.data.validationCode + " topic: " + body.topic);

            // Do any additional validation (as required) and then return back the below response
            var code = body.data.validationCode;
            context.res = { status: 200, body: { "ValidationResponse": code } };
        }

        else if (body.data && body.eventType == storageBlobCreatedEvent) {
            var blobCreatedEventData = body.data;
            context.log("Relaying received blob created event payload:" + JSON.stringify(blobCreatedEventData));
        }
    }
    context.done();
};

测试能否处理“已处理 Blob”事件Test Blob Created event handling

通过将 Blob 存储事件放到测试字段中并运行以下项来测试函数的新功能:Test the new functionality of the function by putting a Blob storage event into the test field and running:

[{
  "topic": "/subscriptions/{subscription-id}/resourceGroups/Storage/providers/Microsoft.Storage/storageAccounts/xstoretestaccount",
  "subject": "/blobServices/default/containers/testcontainer/blobs/testfile.txt",
  "eventType": "Microsoft.Storage.BlobCreated",
  "eventTime": "2017-06-26T18:41:00.9584103Z",
  "id": "831e1650-001e-001b-66ab-eeb76e069631",
  "data": {
    "api": "PutBlockList",
    "clientRequestId": "6d79dbfb-0e37-4fc4-981f-442c9ca65760",
    "requestId": "831e1650-001e-001b-66ab-eeb76e000000",
    "eTag": "0x8D4BCC2E4835CD0",
    "contentType": "text/plain",
    "contentLength": 524288,
    "blobType": "BlockBlob",
    "url": "https://example.blob.core.chinacloudapi.cn/testcontainer/testfile.txt",
    "sequencer": "00000000000004420000000000028963",
    "storageDiagnostics": {
      "batchId": "b68529f3-68cd-4744-baa4-3c0498ec19f0"
    }
  },
  "dataVersion": "",
  "metadataVersion": "1"
}]

函数日志中应该会显示 Blob URL 输出:You should see the blob URL output in the function log:

输出日志

还可以通过以下方式进行测试:创建一个 Blob 存储帐户或通用版 V2 (GPv2) 存储帐户,添加事件订阅,然后将终结点设置为函数 URL:You can also test by creating a Blob storage account or General Purpose V2 (GPv2) Storage account, adding and event subscription, and setting the endpoint to the function URL:

函数 URL

处理自定义事件Handle Custom events

最后,再次扩展函数,使其还能够处理自定义事件。Finally, lets extend the function once more so that it can also handle custom events.

在 C# 中,SDK 支持将事件类型名称映射到事件数据类型。In C#, the SDK supports mapping an event type name to the event data type. AddOrUpdateCustomEventMapping() 函数可用于映射自定义事件。Use the AddOrUpdateCustomEventMapping() function to map the custom event.

添加对事件 Contoso.Items.ItemReceived 的检查。Add a check for your event Contoso.Items.ItemReceived. 最终代码应如下所示:Your final code should look like:

using System.Net;
using Newtonsoft.Json;
using Microsoft.Azure.EventGrid.Models;
using Microsoft.Azure.EventGrid;

class ContosoItemReceivedEventData
{
    [JsonProperty(PropertyName = "itemSku")]
    public string ItemSku { get; set; }
}

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info($"C# HTTP trigger function begun");
    string response = string.Empty;

    string requestContent = await req.Content.ReadAsStringAsync();
    log.Info($"Received events: {requestContent}");

    EventGridSubscriber eventGridSubscriber = new EventGridSubscriber();
    eventGridSubscriber.AddOrUpdateCustomEventMapping("Contoso.Items.ItemReceived", typeof(ContosoItemReceivedEventData));
    EventGridEvent[] eventGridEvents = eventGridSubscriber.DeserializeEventGridEvents(requestContent);

    foreach (EventGridEvent eventGridEvent in eventGridEvents)
    {
        if (eventGridEvent.Data is SubscriptionValidationEventData)
        {
            var eventData = (SubscriptionValidationEventData)eventGridEvent.Data;
            log.Info($"Got SubscriptionValidation event data, validation code: {eventData.ValidationCode}, topic: {eventGridEvent.Topic}");
            // Do any additional validation (as required) and then return back the below response

            var responseData = new SubscriptionValidationResponse()
            {
                ValidationResponse = eventData.ValidationCode
            };

            return req.CreateResponse(HttpStatusCode.OK, responseData);
        }
        else if (eventGridEvent.Data is StorageBlobCreatedEventData)
        {
            var eventData = (StorageBlobCreatedEventData)eventGridEvent.Data;
            log.Info($"Got BlobCreated event data, blob URI {eventData.Url}");
        }
        else if (eventGridEvent.Data is ContosoItemReceivedEventData)
        {
            var eventData = (ContosoItemReceivedEventData)eventGridEvent.Data;
            log.Info($"Got ContosoItemReceived event data, item SKU {eventData.ItemSku}");
        }
    }

    return req.CreateResponse(HttpStatusCode.OK, response);
}
module.exports = function (context, req) {
    context.log('JavaScript HTTP trigger function begun');
    var validationEventType = "Microsoft.EventGrid.SubscriptionValidationEvent";
    var storageBlobCreatedEvent = "Microsoft.Storage.BlobCreated";
    var customEventType = "Contoso.Items.ItemReceived";

    for (var events in req.body) {
        var body = req.body[events];
        // Deserialize the event data into the appropriate type based on event type
        if (body.data && body.eventType == validationEventType) {
            context.log("Got SubscriptionValidation event data, validation code: " + body.data.validationCode + " topic: " + body.topic);

            // Do any additional validation (as required) and then return back the below response
            var code = body.data.validationCode;
            context.res = { status: 200, body: { "ValidationResponse": code } };
        }

        else if (body.data && body.eventType == storageBlobCreatedEvent) {
            var blobCreatedEventData = body.data;
            context.log("Relaying received blob created event payload:" + JSON.stringify(blobCreatedEventData));
        }

        else if (body.data && body.eventType == customEventType) {
            var payload = body.data;
            context.log("Relaying received custom payload:" + JSON.stringify(payload));
        }
    }
    context.done();
};

测试能否处理自定义事件Test custom event handling

最后,测试函数现在能否处理自定义事件类型:Finally, test that your function can now handle your custom event type:

[{
    "subject": "Contoso/foo/bar/items",
    "eventType": "Contoso.Items.ItemReceived",
    "eventTime": "2017-08-16T01:57:26.005121Z",
    "id": "602a88ef-0001-00e6-1233-1646070610ea",
    "data": { 
            "itemSku": "Standard"
            },
    "dataVersion": "",
    "metadataVersion": "1"
}]

还可实时测试此功能,方式是在门户中通过 CURL 发送自定义事件,或者使用任何可通过 POST 发送到终结点(如 Postman)的服务或应用程序发布到自定义主题You can also test this functionality live by sending a custom event with CURL from the Portal or by posting to a custom topic using any service or application that can POST to an endpoint such as Postman. 使用终结点集作为函数 URL,创建自定义主题和事件订阅。Create a custom topic and an event subscription with the endpoint set as the Function URL.

后续步骤Next steps