Azure Event Grid with .NET: Event-Driven Architecture Made Simple

Azure Event Grid is a fully managed event routing service. It connects event sources to event handlers with at-least-once delivery, filtering, and fan-out — without you managing queues, subscriptions, or polling infrastructure.

Where Service Bus is for commands and messages that need guaranteed processing, Event Grid is for events — things that happened. "An order was placed." "A blob was uploaded." "A resource was created." Subscribers react to these events, but the publisher doesn't care who's listening or what they do with it.

Publishing Events

Use the Azure.Messaging.EventGrid SDK. First, decide between Event Grid's native schema and CloudEvents. CloudEvents is the CNCF standard and the recommended choice for new applications:

Example.cs
public class EventPublisher
{
    private readonly EventGridPublisherClient _client;

    public EventPublisher(EventGridPublisherClient client) => _client = client;

    public async Task PublishOrderCreatedAsync(Order order)
    {
        var cloudEvent = new CloudEvent(
            source: "/orders/api",
            type: "Order.Created",
            jsonSerializableData: new
            {
                order.Id,
                order.CustomerId,
                order.Total,
                order.CreatedAt
            });

        await _client.SendEventAsync(cloudEvent);
    }
}

Register the client in DI:

Example.cs
builder.Services.AddSingleton(new EventGridPublisherClient(
    new Uri("https://my-topic.uksouth-1.eventgrid.azure.net/api/events"),
    new DefaultAzureCredential()));

Publishing in Batches

For throughput, publish multiple events in a single call:

Example.cs
public async Task PublishBatchAsync(IEnumerable<Order> orders)
{
    var events = orders.Select(order => new CloudEvent(
        source: "/orders/api",
        type: "Order.Created",
        jsonSerializableData: new { order.Id, order.CustomerId, order.Total }
    )).ToList();

    await _client.SendEventsAsync(events);
}

Event Grid supports batches up to 1 MB. For large event volumes, partition into multiple batch calls.

Handling Events with Azure Functions

The most common subscriber is an Azure Function with an Event Grid trigger:

Example.cs
public class OrderEventHandler
{
    private readonly INotificationService _notifications;
    private readonly ILogger<OrderEventHandler> _logger;

    public OrderEventHandler(INotificationService notifications,
        ILogger<OrderEventHandler> logger)
    {
        _notifications = notifications;
        _logger = logger;
    }

    [Function("HandleOrderCreated")]
    public async Task HandleOrderCreated(
        [EventGridTrigger] CloudEvent cloudEvent)
    {
        var data = cloudEvent.Data?.ToObjectFromJson<OrderCreatedData>();

        if (data is null)
        {
            _logger.LogWarning("Received event with null data");
            return;
        }

        _logger.LogInformation("Processing order {OrderId} for customer {CustomerId}",
            data.Id, data.CustomerId);

        await _notifications.SendOrderConfirmationAsync(data.CustomerId, data.Id);
    }
}

public record OrderCreatedData(string Id, string CustomerId, decimal Total);

Handling Events with Webhooks

For ASP.NET Core applications, handle events via a webhook endpoint:

Example.cs
app.MapPost("/api/webhooks/events", async (HttpRequest request) =>
{
    var events = await BinaryData
        .FromStreamAsync(request.Body)
        .ContinueWith(t => CloudEvent.ParseMany(t.Result));

    foreach (var cloudEvent in events)
    {
        switch (cloudEvent.Type)
        {
            case "Order.Created":
                var order = cloudEvent.Data?.ToObjectFromJson<OrderCreatedData>();
                // Process the order event
                break;

            case "Order.Cancelled":
                var cancellation = cloudEvent.Data?.ToObjectFromJson<OrderCancelledData>();
                // Process the cancellation
                break;
        }
    }

    return Results.Ok();
});

Event Grid validates webhook endpoints using a subscription validation handshake. With CloudEvents, this is handled automatically via an HTTP OPTIONS response. The Azure.Messaging.EventGrid package doesn't require manual validation handling for CloudEvents subscriptions.

Filtering Events

Event subscriptions support filtering to reduce noise at the subscriber:

terminal
az eventgrid event-subscription create \
    --name billing-sub \
    --source-resource-id /subscriptions/.../topics/order-events \
    --endpoint https://my-api.azurewebsites.net/api/webhooks/events \
    --event-delivery-schema cloudeventschemav1_0 \
    --advanced-filter data.Total NumberGreaterThan 100

This subscription only receives events where data.Total exceeds 100. Filtering happens at the Event Grid level — your endpoint isn't invoked for filtered-out events.

System Topics

Event Grid also surfaces events from Azure services themselves. When a blob is uploaded to Storage, a resource is modified in a resource group, or an IoT device sends telemetry — these are system topics:

Example.cs
// Azure Function triggered when a blob is uploaded
[Function("ProcessUpload")]
public async Task ProcessUpload(
    [EventGridTrigger] CloudEvent cloudEvent)
{
    var data = cloudEvent.Data?.ToObjectFromJson<StorageBlobCreatedEventData>();

    if (data is null) return;

    _logger.LogInformation("New blob: {Url}, size: {Size} bytes",
        data.Url, data.ContentLength);

    await _processor.ProcessBlobAsync(data.Url);
}

You don't publish these events yourself — Azure does. You just subscribe to them.

Retry and Dead-Lettering

Event Grid retries delivery with exponential backoff for up to 24 hours. Configure dead-lettering to capture events that exceed retry limits:

terminal
az eventgrid event-subscription update \
    --name billing-sub \
    --deadletter-endpoint /subscriptions/.../storageAccounts/myaccount/blobServices/default/containers/deadletters

Failed events are written to a blob container for later inspection and reprocessing.

Event Grid is the glue for event-driven architectures on Azure. It's low-cost, high-throughput, and requires minimal code. Pair it with Azure Functions for serverless event handling, or webhooks for existing services that need to react to events.