Skip to main content

Overview

The Azure Functions Event Grid module enables Azure Functions to receive and dispatch Event Grid events to event handlers. This provides a serverless, event-driven architecture for reacting to changes across Azure services and custom applications. Azure Event Grid is a fully managed event routing service that enables event-driven, reactive programming using a publish-subscribe model.

Installation

Intent.AzureFunctions.AzureEventGrid

Dependencies

  • Intent.AzureFunctions - Core Azure Functions support
  • Intent.Eventing.AzureEventGrid - Event Grid eventing contracts
  • Intent.Eventing.Contracts - Base eventing infrastructure

Event Grid Basics

Key Concepts

Topics - Endpoints where events are published
Event Subscriptions - Routes that filter and deliver events
Event Handlers - Applications that respond to events
Events - JSON messages describing what happened

Event Grid vs Service Bus

FeatureEvent GridService Bus
PatternEvent distributionMessage queuing
LatencySub-secondSeconds
OrderingNot guaranteedGuaranteed (sessions)
Use CaseEvent notificationsTransaction processing

Modeling Event Grid Consumers

In the Services Designer

  1. Create Integration Events in the Eventing designer
  2. Add Event Grid stereotype to mark events for Event Grid
  3. Model Event Handlers that subscribe to Event Grid events

Generated Consumer

Intent Architect generates Azure Functions that consume Event Grid events:
public class BlobCreatedConsumer
{
    private readonly IIntegrationEventHandler<BlobCreatedEvent> _handler;

    public BlobCreatedConsumer(IIntegrationEventHandler<BlobCreatedEvent> handler)
    {
        _handler = handler;
    }

    [Function("BlobCreatedConsumer")]
    public async Task Run(
        [EventGridTrigger] EventGridEvent eventGridEvent,
        CancellationToken cancellationToken)
    {
        var blobEvent = eventGridEvent.Data.ToObjectFromJson<BlobCreatedEvent>();
        await _handler.HandleAsync(blobEvent, cancellationToken);
    }
}

Event Grid Event Structure

Event Grid events follow this schema:
{
  "id": "unique-event-id",
  "eventType": "Microsoft.Storage.BlobCreated",
  "subject": "/blobServices/default/containers/mycontainer/blobs/myfile.txt",
  "eventTime": "2024-01-15T10:30:00Z",
  "data": {
    "api": "PutBlob",
    "clientRequestId": "request-id",
    "requestId": "server-request-id",
    "url": "https://myaccount.blob.core.windows.net/mycontainer/myfile.txt"
  },
  "dataVersion": "1.0",
  "metadataVersion": "1",
  "topic": "/subscriptions/{subscription-id}/resourceGroups/..."
}

Event Sources

Azure Services

Event Grid supports many Azure service event sources:

Blob Storage

Blob created, deleted, archived

Resource Manager

Resource created, updated, deleted

IoT Hub

Device created, deleted, telemetry

Container Registry

Image pushed, deleted

Custom Topics

Publish custom events to Event Grid:
public class OrderService
{
    private readonly EventGridPublisherClient _client;

    public async Task CreateOrderAsync(CreateOrderCommand command)
    {
        // Create order
        var order = await _repository.CreateAsync(command);
        
        // Publish to Event Grid
        var eventGridEvent = new EventGridEvent(
            subject: $"orders/{order.Id}",
            eventType: "Contoso.Orders.OrderCreated",
            dataVersion: "1.0",
            data: new OrderCreatedEvent
            {
                OrderId = order.Id,
                CustomerId = order.CustomerId,
                Amount = order.TotalAmount
            });

        await _client.SendEventAsync(eventGridEvent);
    }
}

Processing Patterns

Single Event Processing

[Function("ProcessStorageEvent")]
public async Task Run(
    [EventGridTrigger] EventGridEvent eventGridEvent,
    CancellationToken cancellationToken)
{
    _logger.LogInformation(
        "Processing {EventType} event for {Subject}",
        eventGridEvent.EventType,
        eventGridEvent.Subject);

    switch (eventGridEvent.EventType)
    {
        case "Microsoft.Storage.BlobCreated":
            var blobData = eventGridEvent.Data
                .ToObjectFromJson<StorageBlobCreatedEventData>();
            await _handler.HandleBlobCreatedAsync(blobData, cancellationToken);
            break;
            
        case "Microsoft.Storage.BlobDeleted":
            var deletedData = eventGridEvent.Data
                .ToObjectFromJson<StorageBlobDeletedEventData>();
            await _handler.HandleBlobDeletedAsync(deletedData, cancellationToken);
            break;
    }
}

Batch Event Processing

[Function("ProcessEventsBatch")]
public async Task Run(
    [EventGridTrigger] EventGridEvent[] events,
    CancellationToken cancellationToken)
{
    foreach (var evt in events)
    {
        await ProcessEventAsync(evt, cancellationToken);
    }
}

Event Filtering

Filter events at the subscription level (configured in Azure):
{
  "filter": {
    "includedEventTypes": [
      "Microsoft.Storage.BlobCreated",
      "Microsoft.Storage.BlobDeleted"
    ],
    "subjectBeginsWith": "/blobServices/default/containers/important/",
    "subjectEndsWith": ".json",
    "advancedFilters": [
      {
        "operatorType": "NumberGreaterThan",
        "key": "data.contentLength",
        "value": 1024
      }
    ]
  }
}

Event Validation

Subscription Validation

Event Grid validates webhook endpoints:
[Function("EventGridWebhook")]
public async Task<HttpResponseData> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post")] 
    HttpRequestData req,
    CancellationToken cancellationToken)
{
    var events = await req.ReadFromJsonAsync<EventGridEvent[]>(cancellationToken);
    
    foreach (var evt in events)
    {
        // Handle validation event
        if (evt.EventType == "Microsoft.EventGrid.SubscriptionValidationEvent")
        {
            var validationData = evt.Data
                .ToObjectFromJson<SubscriptionValidationEventData>();
            
            var response = req.CreateResponse(HttpStatusCode.OK);
            await response.WriteAsJsonAsync(new
            {
                validationResponse = validationData.ValidationCode
            }, cancellationToken);
            return response;
        }
        
        // Process actual events
        await _handler.HandleAsync(evt, cancellationToken);
    }
    
    return req.CreateResponse(HttpStatusCode.OK);
}

Error Handling & Retry

Automatic Retry

Event Grid automatically retries failed deliveries:
  • Retry Schedule: Exponential backoff
  • Maximum Attempts: 30 attempts over 24 hours
  • Dead Letter: Configure dead-letter destination

Dead Letter Configuration

public async Task ConfigureDeadLetterAsync()
{
    var deadLetterDestination = new StorageBlobDeadLetterDestination
    {
        ResourceId = "/subscriptions/.../blobServices/default",
        BlobContainerName = "eventgrid-deadletter"
    };

    var eventSubscription = new EventGridSubscription
    {
        DeadLetterDestination = deadLetterDestination,
        EventDeliverySchema = EventDeliverySchema.EventGridSchema,
        RetryPolicy = new RetryPolicy
        {
            MaxDeliveryAttempts = 10,
            EventTimeToLiveInMinutes = 1440 // 24 hours
        }
    };
}

Exception Handling

[Function("ProcessEvent")]
public async Task Run(
    [EventGridTrigger] EventGridEvent eventGridEvent,
    CancellationToken cancellationToken)
{
    try
    {
        await _handler.HandleAsync(eventGridEvent, cancellationToken);
    }
    catch (TransientException ex)
    {
        _logger.LogWarning(ex, "Transient error, Event Grid will retry");
        throw; // Let Event Grid retry
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Fatal error processing event {EventId}", 
            eventGridEvent.Id);
        
        // Log to monitoring
        await _monitoring.TrackFailedEventAsync(eventGridEvent, ex);
        
        // Don't throw - event will be dead-lettered after max retries
    }
}

Security

Managed Identity

Use managed identities for Event Grid authentication:
var credential = new DefaultAzureCredential();
var client = new EventGridPublisherClient(
    new Uri("https://mytopic.westus2-1.eventgrid.azure.net/api/events"),
    credential);

Access Keys

Alternatively, use access keys (less recommended):
{
  "EventGrid": {
    "TopicEndpoint": "https://mytopic.westus2-1.eventgrid.azure.net/api/events",
    "TopicKey": "your-access-key"
  }
}

Monitoring

Application Insights

_telemetry.TrackEvent("EventGridEventProcessed", new Dictionary<string, string>
{
    { "EventType", eventGridEvent.EventType },
    { "Subject", eventGridEvent.Subject },
    { "EventId", eventGridEvent.Id },
    { "ProcessingTime", stopwatch.Elapsed.ToString() }
});

Metrics to Monitor

  • Delivery success rate
  • Delivery latency
  • Dead-letter count
  • Failed delivery attempts
  • Event age

Common Scenarios

Blob Storage Processing

[IntentManaged(Mode.Merge)]
public class BlobCreatedEventHandler 
    : IIntegrationEventHandler<BlobCreatedEvent>
{
    private readonly BlobServiceClient _blobClient;
    private readonly ILogger _logger;

    public async Task HandleAsync(
        BlobCreatedEvent message, 
        CancellationToken cancellationToken)
    {
        var blobUrl = new Uri(message.Url);
        var containerName = blobUrl.Segments[1].TrimEnd('/');
        var blobName = string.Join("", blobUrl.Segments.Skip(2));

        var containerClient = _blobClient.GetBlobContainerClient(containerName);
        var blobClient = containerClient.GetBlobClient(blobName);

        // Download and process
        var download = await blobClient.DownloadContentAsync(cancellationToken);
        await ProcessBlobContentAsync(download.Value.Content, cancellationToken);
        
        _logger.LogInformation("Processed blob {BlobName}", blobName);
    }
}

Resource Change Notifications

[IntentManaged(Mode.Merge)]
public class ResourceWriteSuccessHandler 
    : IIntegrationEventHandler<ResourceWriteSuccessEvent>
{
    public async Task HandleAsync(
        ResourceWriteSuccessEvent message, 
        CancellationToken cancellationToken)
    {
        // Audit resource changes
        await _auditLog.RecordChangeAsync(new AuditEntry
        {
            ResourceId = message.ResourceId,
            Operation = message.OperationName,
            Timestamp = message.EventTime,
            Status = message.Status
        }, cancellationToken);
    }
}

Best Practices

Event Grid doesn’t guarantee order. Design handlers to be order-independent or use Event Hubs for ordered events.
Event Grid guarantees at-least-once delivery. Always implement idempotent handlers:
public async Task HandleAsync(BlobCreatedEvent evt, CancellationToken ct)
{
    var processed = await _cache.GetAsync<bool>($"event:{evt.Id}", ct);
    if (processed)
    {
        _logger.LogInformation("Event {EventId} already processed", evt.Id);
        return;
    }
    
    await ProcessEventAsync(evt, ct);
    await _cache.SetAsync($"event:{evt.Id}", true, TimeSpan.FromDays(7), ct);
}
Plan for schema evolution:
  • Use semantic versioning in dataVersion
  • Make new fields optional
  • Handle multiple versions in handlers
  • Use advanced filters to reduce unnecessary events
  • Batch operations when possible
  • Consider consumption plan for variable loads

Local Development

Event Grid Emulator

Use the Event Grid simulator for local testing:
docker run -p 4438:80 \
  -e ASPNETCORE_ENVIRONMENT=Development \
  mcr.microsoft.com/azure-messaging/eventgrid-emulator

ngrok for Webhook Testing

Expose local Functions to Event Grid:
ngrok http 7071
Use the ngrok URL as your Event Grid webhook endpoint.

Resources

Event Grid Triggers

Official trigger documentation

Event Grid Schema

Event schema reference

Event Grid Eventing

Event Grid eventing module

Event Filtering

Advanced filtering guide

Build docs developers (and LLMs) love