Publishing to an Event Grid topic from .Net

By | February 6, 2018

If you’re using Azure’s Event Grid and want to publish a custom event to an Event Grid topic, you need to do so via the REST API.

class EventGridObject
{
    private readonly Uri _endpoint;
    private readonly string _sasKey;

    public EventGridObject(string topicEndpoint, string sasKey, string subject, string eventType, string id = null, DateTime? eventTime = null) : this(new Uri(topicEndpoint), sasKey, subject, eventType, id, eventTime) { }
    public EventGridObject(Uri topicEndpoint, string sasKey, string subject, string eventType, string id = null, DateTime? eventTime = null)
    {
        _endpoint = topicEndpoint ?? throw new ArgumentNullException(nameof(topicEndpoint));
        _sasKey = !string.IsNullOrWhiteSpace(sasKey) ? sasKey : throw new ArgumentNullException(nameof(sasKey));
        Id = string.IsNullOrWhiteSpace(id) ? Guid.NewGuid().ToString() : id;
        Subject = !string.IsNullOrWhiteSpace(subject) ? subject : throw new ArgumentNullException(nameof(subject));
        EventType = !string.IsNullOrWhiteSpace(eventType) ? event
To make this easier, here's a class that'll make this a one-line operation in your code:
Type : throw new ArgumentNullException(nameof(eventType));
        EventTime = eventTime ?? DateTime.UtcNow;
    }

    [JsonProperty(@"data")]
    public object Data { get; set; }
    [JsonProperty(@"id")]
    public string Id { get; }
    [JsonProperty(@"subject")]
    public string Subject { get; }
    [JsonProperty(@"eventType")]
    public string EventType { get; }
    [JsonProperty(@"eventTime")]
    public DateTime EventTime { get; }

    public async Task<HttpResponseMessage> SendAsync()
    {
        using (var c = new HttpClient())
        {
            c.DefaultRequestHeaders.Add(@"aeg-sas-key", _sasKey);

            return await c.PostAsJsonAsync(_endpoint, new[] { this });
        }
    }
}

and you use it like this:

await new EventGridObject(@"https://<grid topic>.westus2-1.eventgrid.azure.net/api/events", @"Yo58SQ...=", @"my/subject/data/goes/here", @"myEventType")
{
    Data = new
    {
        myproperty1 = @"value1",
        myprop2 = variable2,
        queuedTime = DateTime.UtcNow
    }
}.SendAsync();

Update

Since posting this a reader has brought to my attention issues with using HttpClient as a disposable object. Thank you!!

To change the implementation to one more suited to high throughput (are you publishing many, many events to your custom topic?), change the HttpClient’s implementation to avoid using like this:

class EventGridObject
{
    private static readonly HttpClient _httpClient = new HttpClient();  // NEW!

    private readonly Uri _endpoint;
    private readonly string _sasKey;
    
    public EventGridObject(string topicEndpoint, string sasKey, string subject, string eventType, string id = null, DateTime? eventTime = null) : this(new Uri(topicEndpoint), sasKey, subject, eventType, id, eventTime) { }
    public EventGridObject(Uri topicEndpoint, string sasKey, string subject, string eventType, string id = null, DateTime? eventTime = null)
    {
...

    public async Task<HttpResponseMessage> SendAsync()
    {   // DIFFERENT!
        if (_httpClient.DefaultRequestHeaders.Contains(@"aeg-sas-key"))
            _httpClient.DefaultRequestHeaders.Remove(@"aeg-sas-key");
        _httpClient.DefaultRequestHeaders.Add(@"aeg-sas-key", _sasKey);

        return await _httpClient.PostAsJsonAsync(_endpoint, new[] { this });
    }
...

furthermore, if you’re concerned about the possible DNS issue that exists with a singleton HttpClient, the constructor changes as follows:

...
    public EventGridObject(Uri topicEndpoint, string sasKey, string subject, string eventType, string id = null, DateTime? eventTime = null)
    {
        _endpoint = topicEndpoint ?? throw new ArgumentNullException(nameof(topicEndpoint));
        _sasKey = !string.IsNullOrWhiteSpace(sasKey) ? sasKey : throw new ArgumentNullException(nameof(sasKey));
        Id = string.IsNullOrWhiteSpace(id) ? Guid.NewGuid().ToString() : id;
        Subject = !string.IsNullOrWhiteSpace(subject) ? subject : throw new ArgumentNullException(nameof(subject));
        EventType = !string.IsNullOrWhiteSpace(eventType) ? eventType : throw new ArgumentNullException(nameof(eventType));
        EventTime = eventTime ?? DateTime.UtcNow;

        // NEW!
        // http://byterot.blogspot.co.uk/2016/07/singleton-httpclient-dns.html
        System.Net.ServicePointManager.FindServicePoint(_endpoint)?.ConnectionLeaseTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
    }
...

So now the helper looks like:

class EventGridObject
{
    private static readonly HttpClient _httpClient = new HttpClient();

    private readonly Uri _endpoint;
    private readonly string _sasKey;

    public EventGridObject(string topicEndpoint, string sasKey, string subject, string eventType, string id = null, DateTime? eventTime = null) : this(new Uri(topicEndpoint), sasKey, subject, eventType, id, eventTime) { }
    public EventGridObject(Uri topicEndpoint, string sasKey, string subject, string eventType, string id = null, DateTime? eventTime = null)
    {
        _endpoint = topicEndpoint ?? throw new ArgumentNullException(nameof(topicEndpoint));
        _sasKey = !string.IsNullOrWhiteSpace(sasKey) ? sasKey : throw new ArgumentNullException(nameof(sasKey));
        Id = string.IsNullOrWhiteSpace(id) ? Guid.NewGuid().ToString() : id;
        Subject = !string.IsNullOrWhiteSpace(subject) ? subject : throw new ArgumentNullException(nameof(subject));
        EventType = !string.IsNullOrWhiteSpace(eventType) ? eventType : throw new ArgumentNullException(nameof(eventType));
        EventTime = eventTime ?? DateTime.UtcNow;

        // http://byterot.blogspot.co.uk/2016/07/singleton-httpclient-dns.html
        System.Net.ServicePointManager.FindServicePoint(_endpoint).ConnectionLeaseTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
    }

    [JsonProperty(@"data")]
    public object Data { get; set; }
    [JsonProperty(@"id")]
    public string Id { get; }
    [JsonProperty(@"subject")]
    public string Subject { get; }
    [JsonProperty(@"eventType")]
    public string EventType { get; }
    [JsonProperty(@"eventTime")]
    public DateTime EventTime { get; }

    public async Task<HttpResponseMessage> SendAsync()
    {
        if (_httpClient.DefaultRequestHeaders.Contains(@"aeg-sas-key"))
            _httpClient.DefaultRequestHeaders.Remove(@"aeg-sas-key");
        _httpClient.DefaultRequestHeaders.Add(@"aeg-sas-key", _sasKey);

        return await _httpClient.PostAsJsonAsync(_endpoint, new[] { this });
    }
}