Locally Debugging an Event Grid event in an Azure Function

Posted December 6, 2020 in azure-functions event-grid visual-studio
Reading time: 5 minutes

I have a requirement to add a JSON file to Azure Blob Storage and have an Azure Function automatically process it via a BlobTrigger. Simple enough, but when reading the docs for BlobTrigger, I came across this section on Polling (emphasis mine):

In addition, storage logs are created on a “best effort” basis. There’s no guarantee that all events are captured. Under some conditions, logs may be missed.

If you require faster or more reliable blob processing, consider creating a queue message when you create the blob. Then use a queue trigger instead of a blob trigger to process the blob. Another option is to use Event Grid; see the tutorial Automate resizing uploaded images using Event Grid.

I’m not building a banking application, but I still want more reliability than “best effort”, and faster is always nice, so I decided to let an EventGridTrigger handle the blob created event.

The docs for locally debugging an Azure Function with an EventGridTrigger do a great job of explaining how to set up an Event Grid Subscription to call a webhook in response to the blob created event, but at the time I write this, that page is two years old and is missing a critical piece to get local debugging working: you need to use a separate HttpTrigger to parse the posted Event Grid event and handle the webhook validation handshake:

Synchronous handshake: At the time of event subscription creation, Event Grid sends a subscription validation event to your endpoint. The schema of this event is similar to any other Event Grid event. The data portion of this event includes a validationCode property. Your application verifies that the validation request is for an expected event subscription, and returns the validation code in the response synchronously. This handshake mechanism is supported in all Event Grid versions.

You have to have a separate function with an HttpTrigger because the signature of the EventGridTrigger doesn’t allow you to return a 200 OK response with the validation code:

1
2
3
4
5
6
7
8
[FunctionName(nameof(HandleBlobCreatedEvent))]
public static void HandleBlobCreatedEvent([EventGridTrigger] EventGridEvent eventGridEvent, ILogger log)
{
    if (eventGridEvent.Data is StorageBlobCreatedEventData blobCreatedEventData)
    {
        InternalHandleBlobCreatedEvent(blobCreatedEventData, log);
    }
}

I tried changing the signature to return a Task<IActionResult>, but the Functions runtime was NOT happy about that.

So that function can be used in Azure environments when setting up the Event Grid Subscription in Azure Portal, but not for local debugging because there is no way to return the validation code (if you know how to make this happen with an EventGridTrigger, please let me know in the comments).

For local debugging, and to participate in the validation handshake, you need an HttpTrigger function. The Azure team has some nice sample code, but it looks like it targets .NET Framework, so I have adapted it for .NET Core 3.1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[FunctionName(nameof(HandleBlobCreatedEventHttp))]
public static async Task<IActionResult> HandleBlobCreatedEventHttp([HttpTrigger(AuthorizationLevel.Function, "POST")] HttpRequest httpRequest, ILogger log)
{
    // Ready the request body as a string.
    var requestBody = string.Empty;
    using (var streamReader = new StreamReader(httpRequest.Body, Encoding.UTF8))
    {
        requestBody = await streamReader.ReadToEndAsync();
    }

    log.LogInformation($"Received events: {requestBody}");

    // Deserialize the event(s) so that we can process them accordingly.
    var eventGridEvents = new EventGridSubscriber().DeserializeEventGridEvents(requestBody);

    foreach (var eventGridEvent in eventGridEvents)
    {
        if (eventGridEvent.Data is SubscriptionValidationEventData subValEventData)
        {
            // We're setting up a webhook to call our local dev environment via ngrok. We have to
            //   return the validation code in a 200 OK response body.
            log.LogInformation($"Got SubscriptionValidation event data, validationCode: {subValEventData.ValidationCode},  validationUrl: {subValEventData.ValidationUrl}, topic: {eventGridEvent.Topic}");

            // Do any additional validation (as required) such as validating that the Azure resource ID of the topic matches
            // the expected topic and then return back the below response
            
            var responseData = new Microsoft.Azure.EventGrid.Models.SubscriptionValidationResponse()
            {
                ValidationResponse = subValEventData.ValidationCode
            };

            // Return the validationCode to satisfy the required Event Grid webhook validation handshake.
            return new JsonResult(responseData)
            {
                StatusCode = StatusCodes.Status200OK,
            };
        }
        else if (eventGridEvent.Data is StorageBlobCreatedEventData blobCreatedEventData)
        {
            // This is an actual blob created event from Azure Blob Storage. Handle it.
            InternalHandleBlobCreatedEvent(blobCreatedEventData, log);
        }
    }

    // Default response of 200 OK.
    return new ContentResult
    {
        StatusCode = StatusCodes.Status200OK,
        ContentType = "text/plain",
    };
}

You can see in lines 5-14 that we’re manually parsing the request JSON and deserializing it into EventGridEvent instances. We then loop through the array of events and handle any that interest us. Here, there are two that we want to handle:

  1. SubscriptionValidationEventData: this contains the validation code that we need to return in a 200 OK response to complete the Event Grid validation handshake.

  2. StorageBlobCreatedEventData: this contains information about a blob that was just uploaded to your Blob Storage account. To eliminate code duplication, this calls the same InternalHandleBlobCreatedEvent method to handle the event that we use in the “production” function with the EventGridTrigger.

Now when you click Create in Azure Portal to create your Event Grid Subscription, you’ll see in ngrok that the validation handshake succeeded:

Validation Handshake succeeded
Validation Handshake succeeded

And now that Event Grid has validated your webhook, you can set a breakpoint in your HttpTrigger function, upload a blob, and receive the event right in your local debugger:

Breakpoint hit in the Visual Studio debugger
Breakpoint hit in the Visual Studio debugger

And voilà, you are now locally debugging your Event Grid events in your Azure Function.



Comments

comments powered by Disqus