Line data Source code
1 : #region Copyright
2 : // // -----------------------------------------------------------------------
3 : // // <copyright company="Chinchilla Software Limited">
4 : // // Copyright Chinchilla Software Limited. All rights reserved.
5 : // // </copyright>
6 : // // -----------------------------------------------------------------------
7 : #endregion
8 :
9 : using System;
10 : using System.Collections.Generic;
11 : using System.Linq;
12 : using System.Threading;
13 : using System.Threading.Tasks;
14 : using cdmdotnet.Logging;
15 : using Cqrs.Configuration;
16 : using Cqrs.Events;
17 : using Microsoft.AspNet.SignalR;
18 : using Microsoft.AspNet.SignalR.Hubs;
19 :
20 : namespace Cqrs.WebApi.SignalR.Hubs
21 : {
22 : /// <summary>
23 : /// Sends <see cref="IEvent{TAuthenticationToken}">events</see> to different groups of users via a SignalR <see cref="Hub"/>.
24 : /// </summary>
25 : public class NotificationHub
26 : : Hub
27 : , INotificationHub
28 : , ISingleSignOnTokenNotificationHub
29 1 : {
30 : /// <summary>
31 : /// Instantiates a new instance of <see cref="NotificationHub"/>.
32 : /// </summary>
33 1 : public NotificationHub(ILogger logger, ICorrelationIdHelper correlationIdHelper)
34 : {
35 : Logger = logger;
36 : CorrelationIdHelper = correlationIdHelper;
37 : }
38 :
39 : /// <summary>
40 : /// Instantiates a new instance of <see cref="NotificationHub"/>.
41 : /// </summary>
42 1 : public NotificationHub()
43 : {
44 : }
45 :
46 : /// <summary>
47 : /// Gets or sets the <see cref="ILogger"/>.
48 : /// </summary>
49 : public ILogger Logger { get; set; }
50 :
51 : /// <summary>
52 : /// Gets or sets the <see cref="ICorrelationIdHelper"/>.
53 : /// </summary>
54 : public ICorrelationIdHelper CorrelationIdHelper { get; set; }
55 :
56 : /// <summary>
57 : /// The <see cref="Func{String, Guid}"/> that can convert a <see cref="string"/> based authentication token into the <see cref="Guid"/> based user identifier.
58 : /// </summary>
59 : public Func<string, Guid> ConvertUserTokenToUserRsn { get; set; }
60 :
61 : #region Overrides of HubBase
62 :
63 : /// <summary>
64 : /// When the connection connects to this hub instance we register the connection so we can respond back to it.
65 : /// </summary>
66 1 : public override Task OnConnected()
67 : {
68 : return Join();
69 : }
70 :
71 : /// <summary>
72 : /// When the connection reconnects to this hub instance we register the connection so we can respond back to it.
73 : /// </summary>
74 1 : public override Task OnReconnected()
75 : {
76 : return Join();
77 : }
78 :
79 : #endregion
80 :
81 : /// <summary>
82 : /// Gets the authentication token for the user from the incoming hub request looking at first the
83 : /// <see cref="HubCallerContext.RequestCookies"/> and then the <see cref="HubCallerContext.QueryString"/>.
84 : /// The authentication token should have a name matching the value of "Cqrs.Web.AuthenticationTokenName" from <see cref="IConfigurationManager.GetSetting"/>.
85 : /// </summary>
86 1 : protected virtual string UserToken()
87 : {
88 : string userRsn;
89 : Cookie cookie;
90 :
91 : string authenticationTokenName = DependencyResolver.Current.Resolve<IConfigurationManager>().GetSetting("Cqrs.Web.AuthenticationTokenName") ?? "X-Token";
92 :
93 : if (Context.RequestCookies.TryGetValue(authenticationTokenName, out cookie))
94 : userRsn = cookie.Value;
95 : else
96 : userRsn = Context.QueryString[authenticationTokenName];
97 :
98 : return userRsn
99 : .Replace(".", string.Empty)
100 : .Replace("-", string.Empty);
101 : }
102 :
103 : /// <summary>
104 : /// Join the authenticated user to their relevant <see cref="IHubContext.Groups"/>.
105 : /// </summary>
106 1 : protected virtual Task Join()
107 : {
108 : string userToken = UserToken();
109 : string connectionId = Context.ConnectionId;
110 : return Task.Factory.StartNewSafely(() =>
111 : {
112 : Task work = Groups.Add(connectionId, string.Format("User-{0}", userToken));
113 : work.ConfigureAwait(false);
114 : work.Wait();
115 :
116 : CurrentHub
117 : .Clients
118 : .Group(string.Format("User-{0}", userToken))
119 : .registered("User: " + userToken);
120 :
121 : if (ConvertUserTokenToUserRsn != null)
122 : {
123 : try
124 : {
125 : Guid userRsn = ConvertUserTokenToUserRsn(userToken);
126 : work = Groups.Add(connectionId, string.Format("UserRsn-{0}", userRsn));
127 : work.ConfigureAwait(false);
128 : work.Wait();
129 :
130 : CurrentHub
131 : .Clients
132 : .Group(string.Format("UserRsn-{0}", userRsn))
133 : .registered("UserRsn: " + userRsn);
134 :
135 : }
136 : catch (Exception exception)
137 : {
138 : Logger.LogWarning(string.Format("Registering user token '{0}' to a user RSN and into the SignalR group failed.", userToken), exception: exception, metaData: GetAdditionalDataForLogging(userToken));
139 : }
140 : }
141 : });
142 : }
143 :
144 : /// <summary>
145 : /// Gets the current <see cref="IHubContext"/>.
146 : /// </summary>
147 : protected virtual IHubContext CurrentHub
148 : {
149 : get
150 : {
151 : return GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
152 : }
153 : }
154 :
155 : /// <summary>
156 : /// Send out an event to specific user RSNs
157 : /// </summary>
158 : void INotificationHub.SendUsersEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, params Guid[] userRsnCollection)
159 : {
160 : IList<Guid> optimisedUserRsnCollection = (userRsnCollection ?? Enumerable.Empty<Guid>()).ToList();
161 :
162 : Logger.LogDebug(string.Format("Sending a message on the hub for user RSNs [{0}].", string.Join(", ", optimisedUserRsnCollection)));
163 :
164 : try
165 : {
166 : var tokenSource = new CancellationTokenSource();
167 : Task.Factory.StartNewSafely
168 : (
169 : () =>
170 : {
171 : foreach (Guid userRsn in optimisedUserRsnCollection)
172 : {
173 : var metaData = GetAdditionalDataForLogging(userRsn);
174 : try
175 : {
176 : Clients
177 : .Group(string.Format("UserRsn-{0}", userRsn))
178 : .notifyEvent(new { Type = eventData.GetType().FullName, Data = eventData, CorrelationId = CorrelationIdHelper.GetCorrelationId() });
179 : }
180 : catch (TimeoutException exception)
181 : {
182 : Logger.LogWarning("Sending a message on the hub timed-out.", exception: exception, metaData: metaData);
183 : }
184 : catch (Exception exception)
185 : {
186 : Logger.LogError("Sending a message on the hub resulted in an error.", exception: exception, metaData: metaData);
187 : }
188 : }
189 : }, tokenSource.Token
190 : );
191 :
192 : tokenSource.CancelAfter(15 * 1000);
193 : }
194 : catch (Exception exception)
195 : {
196 : foreach (Guid userRsn in optimisedUserRsnCollection)
197 : Logger.LogError("Queueing a message on the hub resulted in an error.", exception: exception, metaData: GetAdditionalDataForLogging(userRsn));
198 : }
199 : }
200 :
201 : /// <summary>
202 : /// Send out an event to specific user token
203 : /// </summary>
204 : void INotificationHub.SendUserEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, string userToken)
205 : {
206 : Logger.LogDebug(string.Format("Sending a message on the hub for user [{0}].", userToken));
207 :
208 : try
209 : {
210 : var tokenSource = new CancellationTokenSource();
211 : Task.Factory.StartNewSafely
212 : (
213 : () =>
214 : {
215 : IDictionary<string, object> metaData = GetAdditionalDataForLogging(userToken);
216 :
217 : try
218 : {
219 : CurrentHub
220 : .Clients
221 : .Group(string.Format("User-{0}", userToken))
222 : .notifyEvent(new { Type = eventData.GetType().FullName, Data = eventData, CorrelationId = CorrelationIdHelper.GetCorrelationId() });
223 : }
224 : catch (TimeoutException exception)
225 : {
226 : Logger.LogWarning("Sending a message on the hub timed-out.", exception: exception, metaData: metaData);
227 : }
228 : catch (Exception exception)
229 : {
230 : Logger.LogError("Sending a message on the hub resulted in an error.", exception: exception, metaData: metaData);
231 : }
232 : }, tokenSource.Token
233 : );
234 :
235 : tokenSource.CancelAfter(15 * 1000);
236 : }
237 : catch (Exception exception)
238 : {
239 : Logger.LogError("Queueing a message on the hub resulted in an error.", exception: exception, metaData: GetAdditionalDataForLogging(userToken));
240 : }
241 : }
242 :
243 : /// <summary>
244 : /// Send out an event to all users
245 : /// </summary>
246 : void INotificationHub.SendAllUsersEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData)
247 : {
248 : Logger.LogDebug("Sending a message on the hub to all users.");
249 :
250 : try
251 : {
252 : var tokenSource = new CancellationTokenSource();
253 : Task.Factory.StartNewSafely
254 : (
255 : () =>
256 : {
257 : try
258 : {
259 : CurrentHub
260 : .Clients
261 : .All
262 : .notifyEvent(new { Type = eventData.GetType().FullName, Data = eventData, CorrelationId = CorrelationIdHelper.GetCorrelationId() });
263 : }
264 : catch (TimeoutException exception)
265 : {
266 : Logger.LogWarning("Sending a message on the hub to all users timed-out.", exception: exception);
267 : }
268 : catch (Exception exception)
269 : {
270 : Logger.LogError("Sending a message on the hub to all users resulted in an error.", exception: exception);
271 : }
272 : }, tokenSource.Token
273 : );
274 :
275 : tokenSource.CancelAfter(15 * 1000);
276 : }
277 : catch (Exception exception)
278 : {
279 : Logger.LogError("Queueing a message on the hub to all users resulted in an error.", exception: exception);
280 : }
281 : }
282 :
283 : /// <summary>
284 : /// Send out an event to all users except the specific user token
285 : /// </summary>
286 : void INotificationHub.SendExceptThisUserEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, string userToken)
287 : {
288 : Logger.LogDebug(string.Format("Sending a message on the hub for all users except user [{0}].", userToken));
289 :
290 : return;
291 :
292 : /*
293 : try
294 : {
295 : var tokenSource = new CancellationTokenSource();
296 : Task.Factory.StartNewSafely
297 : (
298 : () =>
299 : {
300 : var metaData = GetAdditionalDataForLogging(userToken);
301 :
302 : try
303 : {
304 : CurrentHub
305 : .Clients
306 : .Group(string.Format("User-{0}", userToken))
307 : .notifyEvent(new { Type = eventData.GetType().FullName, Data = eventData, CorrelationId = CorrelationIdHelper.GetCorrelationId() });
308 : }
309 : catch (TimeoutException exception)
310 : {
311 : Logger.LogWarning(string.Format("Sending a message on the hub for all users except user [{0}] timed-out.", userToken), exception: exception, metaData: metaData);
312 : }
313 : catch (Exception exception)
314 : {
315 : Logger.LogError(string.Format("Sending a message on the hub for all users except user [{0}] resulted in an error.", userToken), exception: exception, metaData: metaData);
316 : }
317 : }, tokenSource.Token
318 : );
319 :
320 : tokenSource.CancelAfter(15 * 1000);
321 : }
322 : catch (Exception exception)
323 : {
324 : Logger.LogError("Queueing a message on the hub resulted in an error.", exception: exception, metaData: GetAdditionalDataForLogging(userToken));
325 : }
326 : */
327 : }
328 :
329 : /// <summary>
330 : /// Send out an event to specific user token
331 : /// </summary>
332 : void ISingleSignOnTokenNotificationHub.SendUserEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, string userToken)
333 : {
334 : ((INotificationHub) this).SendUserEvent(eventData, userToken);
335 : }
336 :
337 : /// <summary>
338 : /// Send out an event to all users
339 : /// </summary>
340 : void ISingleSignOnTokenNotificationHub.SendAllUsersEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData)
341 : {
342 : ((INotificationHub)this).SendAllUsersEvent(eventData);
343 : }
344 :
345 : /// <summary>
346 : /// Send out an event to all users except the specific user token
347 : /// </summary>
348 : void ISingleSignOnTokenNotificationHub.SendExceptThisUserEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, string userToken)
349 : {
350 : ((INotificationHub)this).SendExceptThisUserEvent(eventData, userToken);
351 : }
352 :
353 : /// <summary>
354 : /// Send out an event to specific user RSNs
355 : /// </summary>
356 : void ISingleSignOnTokenNotificationHub.SendUsersEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, params Guid[] userRsnCollection)
357 : {
358 : ((INotificationHub)this).SendUsersEvent(eventData, userRsnCollection);
359 : }
360 :
361 : /// <summary>
362 : /// Create additional data containing the provided <paramref name="userRsn"/>.
363 : /// </summary>
364 1 : protected virtual IDictionary<string, object> GetAdditionalDataForLogging(Guid userRsn)
365 : {
366 : return new Dictionary<string, object> { { "UserRsn", userRsn } };
367 : }
368 :
369 : /// <summary>
370 : /// Create additional data containing the provided <paramref name="userToken"/>.
371 : /// </summary>
372 1 : protected virtual IDictionary<string, object> GetAdditionalDataForLogging(string userToken)
373 : {
374 : return new Dictionary<string, object> { { "UserToken", userToken } };
375 : }
376 : }
377 : }
|