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.Replace(".", string.Empty);
99 : }
100 :
101 : /// <summary>
102 : /// Join the authenticated user to their relevant <see cref="IHubContext.Groups"/>.
103 : /// </summary>
104 1 : protected virtual Task Join()
105 : {
106 : string userToken = UserToken();
107 : string connectionId = Context.ConnectionId;
108 : return Task.Factory.StartNewSafely(() =>
109 : {
110 : Task work = Groups.Add(connectionId, string.Format("User-{0}", userToken));
111 : work.ConfigureAwait(false);
112 : work.Wait();
113 :
114 : CurrentHub
115 : .Clients
116 : .Group(string.Format("User-{0}", userToken))
117 : .registered("User: " + userToken);
118 :
119 : if (ConvertUserTokenToUserRsn != null)
120 : {
121 : try
122 : {
123 : Guid userRsn = ConvertUserTokenToUserRsn(userToken);
124 : work = Groups.Add(connectionId, string.Format("UserRsn-{0}", userRsn));
125 : work.ConfigureAwait(false);
126 : work.Wait();
127 :
128 : CurrentHub
129 : .Clients
130 : .Group(string.Format("UserRsn-{0}", userRsn))
131 : .registered("UserRsn: " + userRsn);
132 :
133 : }
134 : catch (Exception exception)
135 : {
136 : Logger.LogWarning(string.Format("Registering user token '{0}' to a user RSN and into the SignalR group failed.", userToken), exception: exception, metaData: GetAdditionalDataForLogging(userToken));
137 : }
138 : }
139 : });
140 : }
141 :
142 : /// <summary>
143 : /// Gets the current <see cref="IHubContext"/>.
144 : /// </summary>
145 : protected virtual IHubContext CurrentHub
146 : {
147 : get
148 : {
149 : return GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
150 : }
151 : }
152 :
153 : /// <summary>
154 : /// Send out an event to specific user RSNs
155 : /// </summary>
156 : void INotificationHub.SendUsersEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, params Guid[] userRsnCollection)
157 : {
158 : IList<Guid> optimisedUserRsnCollection = (userRsnCollection ?? Enumerable.Empty<Guid>()).ToList();
159 :
160 : Logger.LogDebug(string.Format("Sending a message on the hub for user RSNs [{0}].", string.Join(", ", optimisedUserRsnCollection)));
161 :
162 : try
163 : {
164 : var tokenSource = new CancellationTokenSource();
165 : Task.Factory.StartNewSafely
166 : (
167 : () =>
168 : {
169 : foreach (Guid userRsn in optimisedUserRsnCollection)
170 : {
171 : var metaData = GetAdditionalDataForLogging(userRsn);
172 : try
173 : {
174 : Clients
175 : .Group(string.Format("UserRsn-{0}", userRsn))
176 : .notifyEvent(new { Type = eventData.GetType().FullName, Data = eventData, CorrelationId = CorrelationIdHelper.GetCorrelationId() });
177 : }
178 : catch (TimeoutException exception)
179 : {
180 : Logger.LogWarning("Sending a message on the hub timed-out.", exception: exception, metaData: metaData);
181 : }
182 : catch (Exception exception)
183 : {
184 : Logger.LogError("Sending a message on the hub resulted in an error.", exception: exception, metaData: metaData);
185 : }
186 : }
187 : }, tokenSource.Token
188 : );
189 :
190 : tokenSource.CancelAfter(15 * 1000);
191 : }
192 : catch (Exception exception)
193 : {
194 : foreach (Guid userRsn in optimisedUserRsnCollection)
195 : Logger.LogError("Queueing a message on the hub resulted in an error.", exception: exception, metaData: GetAdditionalDataForLogging(userRsn));
196 : }
197 : }
198 :
199 : /// <summary>
200 : /// Send out an event to specific user token
201 : /// </summary>
202 : void INotificationHub.SendUserEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, string userToken)
203 : {
204 : Logger.LogDebug(string.Format("Sending a message on the hub for user [{0}].", userToken));
205 :
206 : try
207 : {
208 : var tokenSource = new CancellationTokenSource();
209 : Task.Factory.StartNewSafely
210 : (
211 : () =>
212 : {
213 : IDictionary<string, object> metaData = GetAdditionalDataForLogging(userToken);
214 :
215 : try
216 : {
217 : CurrentHub
218 : .Clients
219 : .Group(string.Format("User-{0}", userToken))
220 : .notifyEvent(new { Type = eventData.GetType().FullName, Data = eventData, CorrelationId = CorrelationIdHelper.GetCorrelationId() });
221 : }
222 : catch (TimeoutException exception)
223 : {
224 : Logger.LogWarning("Sending a message on the hub timed-out.", exception: exception, metaData: metaData);
225 : }
226 : catch (Exception exception)
227 : {
228 : Logger.LogError("Sending a message on the hub resulted in an error.", exception: exception, metaData: metaData);
229 : }
230 : }, tokenSource.Token
231 : );
232 :
233 : tokenSource.CancelAfter(15 * 1000);
234 : }
235 : catch (Exception exception)
236 : {
237 : Logger.LogError("Queueing a message on the hub resulted in an error.", exception: exception, metaData: GetAdditionalDataForLogging(userToken));
238 : }
239 : }
240 :
241 : /// <summary>
242 : /// Send out an event to all users
243 : /// </summary>
244 : void INotificationHub.SendAllUsersEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData)
245 : {
246 : Logger.LogDebug("Sending a message on the hub to all users.");
247 :
248 : try
249 : {
250 : var tokenSource = new CancellationTokenSource();
251 : Task.Factory.StartNewSafely
252 : (
253 : () =>
254 : {
255 : try
256 : {
257 : CurrentHub
258 : .Clients
259 : .All
260 : .notifyEvent(new { Type = eventData.GetType().FullName, Data = eventData, CorrelationId = CorrelationIdHelper.GetCorrelationId() });
261 : }
262 : catch (TimeoutException exception)
263 : {
264 : Logger.LogWarning("Sending a message on the hub to all users timed-out.", exception: exception);
265 : }
266 : catch (Exception exception)
267 : {
268 : Logger.LogError("Sending a message on the hub to all users resulted in an error.", exception: exception);
269 : }
270 : }, tokenSource.Token
271 : );
272 :
273 : tokenSource.CancelAfter(15 * 1000);
274 : }
275 : catch (Exception exception)
276 : {
277 : Logger.LogError("Queueing a message on the hub to all users resulted in an error.", exception: exception);
278 : }
279 : }
280 :
281 : /// <summary>
282 : /// Send out an event to all users except the specific user token
283 : /// </summary>
284 : void INotificationHub.SendExceptThisUserEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, string userToken)
285 : {
286 : Logger.LogDebug(string.Format("Sending a message on the hub for all users except user [{0}].", userToken));
287 :
288 : return;
289 :
290 : /*
291 : try
292 : {
293 : var tokenSource = new CancellationTokenSource();
294 : Task.Factory.StartNewSafely
295 : (
296 : () =>
297 : {
298 : var metaData = GetAdditionalDataForLogging(userToken);
299 :
300 : try
301 : {
302 : CurrentHub
303 : .Clients
304 : .Group(string.Format("User-{0}", userToken))
305 : .notifyEvent(new { Type = eventData.GetType().FullName, Data = eventData, CorrelationId = CorrelationIdHelper.GetCorrelationId() });
306 : }
307 : catch (TimeoutException exception)
308 : {
309 : Logger.LogWarning(string.Format("Sending a message on the hub for all users except user [{0}] timed-out.", userToken), exception: exception, metaData: metaData);
310 : }
311 : catch (Exception exception)
312 : {
313 : 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);
314 : }
315 : }, tokenSource.Token
316 : );
317 :
318 : tokenSource.CancelAfter(15 * 1000);
319 : }
320 : catch (Exception exception)
321 : {
322 : Logger.LogError("Queueing a message on the hub resulted in an error.", exception: exception, metaData: GetAdditionalDataForLogging(userToken));
323 : }
324 : */
325 : }
326 :
327 : /// <summary>
328 : /// Send out an event to specific user token
329 : /// </summary>
330 : void ISingleSignOnTokenNotificationHub.SendUserEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, string userToken)
331 : {
332 : ((INotificationHub) this).SendUserEvent(eventData, userToken);
333 : }
334 :
335 : /// <summary>
336 : /// Send out an event to all users
337 : /// </summary>
338 : void ISingleSignOnTokenNotificationHub.SendAllUsersEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData)
339 : {
340 : ((INotificationHub)this).SendAllUsersEvent(eventData);
341 : }
342 :
343 : /// <summary>
344 : /// Send out an event to all users except the specific user token
345 : /// </summary>
346 : void ISingleSignOnTokenNotificationHub.SendExceptThisUserEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, string userToken)
347 : {
348 : ((INotificationHub)this).SendExceptThisUserEvent(eventData, userToken);
349 : }
350 :
351 : /// <summary>
352 : /// Send out an event to specific user RSNs
353 : /// </summary>
354 : void ISingleSignOnTokenNotificationHub.SendUsersEvent<TSingleSignOnToken>(IEvent<TSingleSignOnToken> eventData, params Guid[] userRsnCollection)
355 : {
356 : ((INotificationHub)this).SendUsersEvent(eventData, userRsnCollection);
357 : }
358 :
359 : /// <summary>
360 : /// Create additional data containing the provided <paramref name="userRsn"/>.
361 : /// </summary>
362 1 : protected virtual IDictionary<string, object> GetAdditionalDataForLogging(Guid userRsn)
363 : {
364 : return new Dictionary<string, object> { { "UserRsn", userRsn } };
365 : }
366 :
367 : /// <summary>
368 : /// Create additional data containing the provided <paramref name="userToken"/>.
369 : /// </summary>
370 1 : protected virtual IDictionary<string, object> GetAdditionalDataForLogging(string userToken)
371 : {
372 : return new Dictionary<string, object> { { "UserToken", userToken } };
373 : }
374 : }
375 : }
|