-
Notifications
You must be signed in to change notification settings - Fork 4.5k
/
EventGridSubscriber.cs
240 lines (213 loc) · 11.7 KB
/
EventGridSubscriber.cs
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
using Microsoft.Azure.EventGrid.Models;
using Microsoft.Rest.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
namespace Microsoft.Azure.EventGrid
{
public class EventGridSubscriber : IEventGridEventDeserializer, ICustomEventTypeMapper
{
static readonly JsonSerializer defaultJsonSerializer;
readonly ConcurrentDictionary<string, Type> customEventTypeMapping;
static EventGridSubscriber()
{
defaultJsonSerializer = GetJsonSerializerWithPolymorphicSupport();
}
public EventGridSubscriber()
{
customEventTypeMapping = new ConcurrentDictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Deserializes the provided event data using a default JSON serializer that supports all system event types.
/// A webhook/function that is consuming events can call this function to deserialize EventGrid events.
/// For system events, the Data property of each event in the returned array will be set to the appropriate
/// type (e.g. StorageBlobCreatedEventData). For events on custom topics where the type of the Data property
/// can be of any type, the calling function will have to first add a custom event mapping before calling this function.
/// </summary>
/// <param name="requestContent">The JSON string containing an array of EventGrid events</param>
/// <returns>A list of EventGrid Events</returns>
public EventGridEvent[] DeserializeEventGridEvents(string requestContent)
{
return this.DeserializeEventGridEvents(requestContent, defaultJsonSerializer);
}
/// <summary>
/// Deserializes the provided event data using a custom JSON serializer.
/// A webhook/function that is consuming events can call this function to deserialize EventGrid events.
/// For system events, the Data property of each event in the returned array will be set to the appropriate
/// type (e.g. StorageBlobCreatedEventData). For events on custom topics where the type of the Data property
/// can be of any type, the calling function will have to first add a custom event mapping before calling this function.
/// </summary>
/// <param name="requestContent">The JSON string containing an array of EventGrid events</param>
/// <param name="jsonSerializer">JsonSerializer to use for the deserialization.</param>
/// <returns>A list of EventGrid Events</returns>
public EventGridEvent[] DeserializeEventGridEvents(string requestContent, JsonSerializer jsonSerializer)
{
EventGridEvent[] eventGridEvents = JsonConvert.DeserializeObject<EventGridEvent[]>(requestContent, jsonSerializer.GetJsonSerializerSettings());
return DeserializeEventGridEventData(eventGridEvents, jsonSerializer);
}
/// <summary>
/// Deserializes the provided stream using a default JSON serializer that supports all system event types.
/// A webhook/function that is consuming events can call this function to deserialize EventGrid events.
/// For system events, the Data property of each event in the returned array will be set to the appropriate
/// type (e.g. StorageBlobCreatedEventData). For events on custom topics where the type of the Data property
/// can be of any type, the calling function will have to first add a custom event mapping before calling this function.
/// </summary>
/// <param name="requestStream">Request Stream</param>
/// <returns>A list of EventGrid Events</returns>
public EventGridEvent[] DeserializeEventGridEvents(Stream requestStream)
{
return this.DeserializeEventGridEvents(requestStream, defaultJsonSerializer);
}
/// <summary>
/// Deserializes the provided stream using a custom JSON serializer.
/// A webhook/function that is consuming events can call this function to deserialize EventGrid events.
/// For system events, the Data property of each event in the returned array will be set to the appropriate
/// type (e.g. StorageBlobCreatedEventData). For events on custom topics where the type of the Data property
/// can be of any type, the calling function will have to first add a custom event mapping before calling this function.
/// </summary>
/// <param name="requestStream">Request Stream</param>
/// <param name="jsonSerializer">JsonSerializer to use for the deserialization.</param>
/// <returns>A list of EventGrid Events</returns>
public EventGridEvent[] DeserializeEventGridEvents(Stream requestStream, JsonSerializer jsonSerializer)
{
EventGridEvent[] eventGridEvents = null;
using (var streamReader = new StreamReader(requestStream))
{
using (var jsonTextReader = new JsonTextReader(streamReader))
{
eventGridEvents = (EventGridEvent[])jsonSerializer.Deserialize(jsonTextReader, typeof(EventGridEvent[]));
}
}
return DeserializeEventGridEventData(eventGridEvents, jsonSerializer);
}
/// <summary>
/// Adds or updates a custom event mapping that associates an eventType string with the corresponding type of event data.
/// </summary>
/// <param name="eventType">The event type to register, such as "Contoso.Items.ItemReceived"</param>
/// <param name="eventDataType">The type of eventdata corresponding to this eventType, such as typeof(ContosoItemReceivedEventData)</param>
public void AddOrUpdateCustomEventMapping(string eventType, Type eventDataType)
{
this.ValidateEventType(eventType);
if (eventDataType == null)
{
throw new ArgumentNullException(nameof(eventDataType));
}
this.customEventTypeMapping.AddOrUpdate(
eventType,
eventDataType,
(_, existingValue) => eventDataType);
}
/// <summary>
/// Gets information about a custom event mapping.
/// </summary>
/// <param name="eventType">The registered event type, such as "Contoso.Items.ItemReceived"</param>
/// <param name="eventDataType">The type of eventdata corresponding to this eventType, such as typeof(ContosoItemReceivedEventData)</param>
/// <returns>True if the specified mapping exists.</returns>
public bool TryGetCustomEventMapping(string eventType, out Type eventDataType)
{
this.ValidateEventType(eventType);
return this.customEventTypeMapping.TryGetValue(eventType, out eventDataType);
}
/// <summary>
/// List all registered custom event mappings.
/// </summary>
/// <returns>An IEnumerable of mappings</returns>
public IEnumerable<KeyValuePair<string, Type>> ListAllCustomEventMappings()
{
foreach (KeyValuePair<string, Type> kvp in this.customEventTypeMapping)
{
yield return kvp;
}
}
/// <summary>
/// Removes a custom event mapping.
/// </summary>
/// <param name="eventType">The registered event type, such as "Contoso.Items.ItemReceived"</param>
/// <param name="eventDataType">The type of eventdata corresponding to this eventType, such as typeof(ContosoItemReceivedEventData)</param>
/// <returns>True if the specified mapping was removed successfully.</returns>
public bool TryRemoveCustomEventMapping(string eventType, out Type eventDataType)
{
this.ValidateEventType(eventType);
return this.customEventTypeMapping.TryRemove(eventType, out eventDataType);
}
void ValidateEventType(string eventType)
{
if (string.IsNullOrEmpty(eventType))
{
throw new ArgumentNullException(nameof(eventType));
}
}
EventGridEvent[] DeserializeEventGridEventData(EventGridEvent[] eventGridEvents, JsonSerializer jsonSerializer)
{
foreach (EventGridEvent receivedEvent in eventGridEvents)
{
Type typeOfEventData = null;
// First, let's attempt to find the mapping for the event type in the system event type mapping.
// Note that system event data would always be of type JObject.
if (SystemEventTypeMappings.SystemEventMappings.TryGetValue(receivedEvent.EventType, out typeOfEventData))
{
JObject dataObject = receivedEvent.Data as JObject;
if (dataObject != null)
{
var eventData = dataObject.ToObject(typeOfEventData, jsonSerializer);
receivedEvent.Data = eventData;
}
}
// If not a system event, let's attempt to find the mapping for the event type in the custom event mapping.
else if (this.TryGetCustomEventMapping(receivedEvent.EventType, out typeOfEventData))
{
JToken dataToken = receivedEvent.Data as JToken;
if (dataToken == null)
{
// Nothing to do (e.g. this will happen if Data is a primitive/string type).
continue;
}
switch (dataToken.Type)
{
case JTokenType.Object:
var eventData = dataToken.ToObject(typeOfEventData, jsonSerializer);
receivedEvent.Data = eventData;
break;
case JTokenType.Array:
var arrayEventData = JsonConvert.DeserializeObject(
dataToken.ToString(),
typeOfEventData,
jsonSerializer.GetJsonSerializerSettings());
receivedEvent.Data = arrayEventData;
break;
default:
break;
}
}
}
return eventGridEvents;
}
static JsonSerializer GetJsonSerializerWithPolymorphicSupport()
{
JsonSerializerSettings deserializationSettings = new JsonSerializerSettings
{
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
Converters = new List<JsonConverter>
{
new Iso8601TimeSpanConverter()
}
};
JsonSerializer jsonSerializer = JsonSerializer.Create(deserializationSettings);
// Note: If any of the events have polymorphic data, add converters for them here.
// This enables the polymorphic deserialization for the event data.
// For example, MediaJobCompletedEventData's MediaJobOutput type is polymorphic
// based on the @odata.type property in the data.
jsonSerializer.Converters.Add(new PolymorphicDeserializeJsonConverter<MediaJobOutput>("@odata.type"));
return jsonSerializer;
}
}
}