Azure Functions provides the built-in binding extensions feature for Azure EventGrid, which makes event data easier to publish and consume. But, the binding extension doesn't support the CloudEvents format yet because it uses the current version of SDK that doesn't support the CloudEvents schema yet. I suspect the binding extension will be available for CloudEvents when a new version of SDK becomes GA. Therefore, it requires a few extra coding efforts to send and receive CloudEvents data via Azure Functions app for now. Throughout this post, I will discuss how to make it.
I used .NET SDK in this post. But there are other SDKs in different languages of your choice:
Installing Azure EventGrid SDK Preview
At the time of this writing, the new SDK has the version of 4.0.0-beta.4
–still in preview. With this SDK, you can send and receive event data in the format of CloudEvents. Install the preview version of the SDK by entering the following command:
dotnet add package Azure.Messaging.EventGrid --version 4.0.0-beta.4 |
Let's deal with the EventGrid data.
Sending Event Data in CloudEvents Format
To use EventGrid related commands in Azure CLI, you should install the relevant extension first:
az extension add -n eventgrid |
Then, get the endpoint of your EventGrid Custom Topic:
endpoint=$(az eventgrid topic show \ | |
-g <resource_group_name> \ | |
-n <eventgrid_topic_name> \ | |
--query "endpoint" -o tsv) |
And, get the access key:
id=$(az eventgrid topic show \ | |
-g <resource_group_name> \ | |
-n <eventgrid_topic_name> \ | |
--query "id" -o tsv) | |
key=$(az rest \ | |
-m POST \ | |
-u "https://management.azure.com$id/listKeys?api-version=2020-06-01" \ | |
--query "key1" -o tsv) |
With both endpoint and access key you got from the commands above, create the EventGrid publisher instance in your Azure Functions code:
var topicEndpoint = new Uri("<endpoint>"); | |
var credential = new AzureKeyCredential("<key>"); | |
var publisher = new EventGridPublisherClient(topicEndpoint, credential); |
You need more metadata details to send event data in the CloudEvents format.
source
: Event Publisher–It's a URL format in general.type
: Event Type–It distinguishes events from each other. It usually has a format likecom.example.someevent
.datacontenttype
: Always provideapplication/cloudevents+json
.
The SDK takes care of the rest of the metadata that CloudEvents needs.
If you want to know more about the CloudEvents data spec, visit this page and have a read.
With those metadata details, build up the CloudEvents instances and send them to Azure EvengGrid.
var source = "<source>"; | |
var type = "<type>"; | |
var dataContentType = "application/cloudevents+json"; | |
var data = new MyData() { Hello = "World" }; | |
var @event = new CloudEvent(source, type, data, dataContentType); | |
var events = new List<CloudEvent>() { @event }; | |
await publisher.SendEventsAsync(events).ConfigureAwait(false); |
You can now send the event data in the CloudEvents format to EventGrid Custom Topic.
Receiving Event Data in CloudEvents Format
As mentioned above, Azure Functions has the EventGrid binding extension and it doesn't support CloudEvents yet. Therefore, to process the CloudEvents data, you should use HTTP Trigger. And the trigger MUST handle two different types of requests at the same time.
- Responding Event Handler Validation Requests
- Handling Event Data
Responding Event Handler Validation Requests
According to the CloudEvents Webhook Spec, the validation requests uses the OPTIONS
method/verb and include a request header of WebHook-Request-Origin
(line #8). Therefore, to respond to the validation request, the header value MUST be sent back to the EventGrid Topic in the response header of WebHook-Allowed-Origin
(line #9).
[FunctionName("HttpTrigger")] | |
public static async Task<IActionResult> Run( | |
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", "OPTIONS", Route = "handler")] HttpRequest req, | |
ILogger log) | |
{ | |
if (HttpMethods.IsOptions(req.Method)) | |
{ | |
string header = req.Headers["WebHook-Request-Origin"]; | |
req.HttpContext.Response.Headers.Add("Webhook-Allowed-Origin", header); | |
return new OkResult(); | |
} | |
... | |
} |
Handling Event Data
Once the validation request is successful, the Azure Functions endpoint will receive the event data with the POST
method/verb from there. If you want to use the CloudEvents itself, use the @event
instance in the following code snippet (line #18). If you only need the data
part, deserialise it (line #19).
[FunctionName("HttpTrigger")] | |
public static async Task<IActionResult> Run( | |
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", "OPTIONS", Route = "handler")] HttpRequest req, | |
ILogger log) | |
{ | |
if (HttpMethods.IsOptions(req.Method)) | |
{ | |
... | |
} | |
using (var reader = new StreamReader(req.Body)) | |
{ | |
var payload = await reader.ReadToEndAsync().ConfigureAwait(false); | |
var events = CloudEvent.Parse(payload); | |
foreach (var @event in events) | |
{ | |
var timestamp = @event.Time; | |
var data = await @event.GetDataAsync<MyData>().ConfigureAwait(false); | |
... | |
} | |
} | |
return new OkResult(); | |
} |
You can now receive and process the event data in the CloudEvents format within the Azure Functions code.
So far, we have walked through how Azure Functions app sends and receive event data formatted in CloudEvents for Azure EventGrid. Until the new version of the binding extension is released, we should stick on the approach like this. Hope the new one is released sooner rather than later.