-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathSession.cs
More file actions
469 lines (432 loc) · 16.1 KB
/
Copy pathSession.cs
File metadata and controls
469 lines (432 loc) · 16.1 KB
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
using Microsoft.AspNetCore.Http;
using System.Dynamic;
using System.Reflection;
using UISupportGeneric.Resources;
using UISupportGeneric.UI;
namespace UISupportBlazor
{
/// <summary>
/// Represents a user session with associated values and timeout functionality
/// </summary>
public class Session
{
private static IHttpContextAccessor? _httpContextAccessor;
/// <summary>
/// Configures the session with the provided IHttpContextAccessor
/// </summary>
/// <param name="httpContextAccessor"></param>
public static void Configure(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// Checks if the session is configured
/// </summary>
public static bool IsConfigured => _httpContextAccessor != null;
/// <summary>
/// Gets the current HttpContext
/// </summary>
/// <returns>Current HttpContext</returns>
public static HttpContext? GetCurrentHttpContext()
{
return _httpContextAccessor?.HttpContext;
}
/// <summary>
/// Static class managing all active sessions and providing session-related operations
/// </summary>
static public class Sessions
{
/// <summary>
/// Event triggered when a new session is created
/// </summary>
static public event Action<Session>? OnNewSession;
/// <summary>
/// Event triggered when a session expires
/// </summary>
static public event Action<Session>? OnSessionExpired;
/// <summary>
/// Obtain session object of current user
/// </summary>
/// <param name="context">Current HttpContext</param>
/// <returns>Session object of current user</returns>
static public Session? GetSession(HttpContext? context = null)
{
context ??= GetCurrentHttpContext();
if (context == null)
{
return null;
}
return RefreshSessionTimeOut(context);
}
// Internal method to trigger session expiration event
internal static void Expired(Session session) => OnSessionExpired?.Invoke(session);
// Default session timeout duration (30 minutes)
public static TimeSpan TimeOut = TimeSpan.FromMinutes(30);
// Dictionary storing all active sessions by their GUID
internal static Dictionary<Guid, Session> ActiveSessions = [];
/// <summary>
/// Refreshes the timeout for a session identified by GUID
/// Creates new session if it doesn't exist
/// </summary>
/// <param name="id">Session GUID</param>
/// <returns>The existing or newly created session</returns>
internal static Session RefreshSessionTimeOut(Guid id)
{
if (ActiveSessions.TryGetValue(id, out var session))
{
session.Refresh();
}
else
{
session = new Session(id);
OnNewSession?.Invoke(session);
}
return session;
}
/// <summary>
/// Refreshes the timeout for a session identified by HttpContext
/// Creates new session if it doesn't exist
/// </summary>
/// <param name="context">Current HttpContext</param>
/// <returns>The existing or newly created session</returns>
static internal Session RefreshSessionTimeOut(HttpContext context)
{
Guid sessionId;
var id = (string)context.Items["s"];
id ??= GetCookie(context, "s");
if (id != null)
{
sessionId = new Guid(id);
}
else
{
sessionId = Guid.NewGuid();
context.Items.Add("s", sessionId.ToString());
SetCookie(context, "s", sessionId.ToString());
}
return RefreshSessionTimeOut(sessionId);
}
/// <summary>
/// Sets a cookie with secure options
/// </summary>
/// <param name="context">Current HttpContext</param>
/// <param name="key">Cookie name</param>
/// <param name="value">Cookie value</param>
static public bool SetCookie(HttpContext context, string key, string value)
{
if (!context.Response.HasStarted)
{
var options = new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
MaxAge = TimeOut,
};
context.Response.Cookies.Delete(key);
context.Response.Cookies.Append(key, value, options);
return true;
}
return false;
}
/// <summary>
/// Retrieves a cookie value by key
/// </summary>
/// <param name="context">Current HttpContext</param>
/// <param name="key">Cookie name</param>
/// <returns>Cookie value if found, null otherwise</returns>
static public string? GetCookie(HttpContext context, string key)
{
if (context.Request.Cookies.TryGetValue(key, out var value))
{
return value;
}
return null;
}
/// <summary>
/// Sets a value in the session's dictionary
/// </summary>
/// <param name="id">Session GUID</param>
/// <param name="name">Value name</param>
/// <param name="value">Value to store</param>
public static void SetValue(Guid id, string name, object value)
{
if (ActiveSessions.TryGetValue(id, out var session))
{
lock (session.Values)
{
session.Values[name] = value;
}
}
}
/// <summary>
/// Retrieves a value from the session's dictionary
/// </summary>
/// <param name="id">Session GUID</param>
/// <param name="name">Value name</param>
/// <returns>Stored value if found, null otherwise</returns>
public static object? GetValue(Guid id, string name)
{
if (ActiveSessions.TryGetValue(id, out var session))
{
lock (session.Values)
{
if (session.Values.TryGetValue(name, out var value))
return value;
}
}
return null;
}
}
/// <summary>
/// Gets the current session for the provided HttpContext
/// </summary>
/// <param name="httpContext">Current HttpContext</param>
/// <returns>Session object or null if the session cannot be obtained</returns>
static public Session? Current(HttpContext? httpContext = null)
{
if (httpContext == null)
{
httpContext = GetCurrentHttpContext();
if (httpContext == null)
{
return null;
}
}
var session = Sessions.GetSession(httpContext);
return session;
}
/// <summary>
/// Gets panel-related values from the session
/// Can return panels collection, single panel, or panel element value
/// </summary>
/// <param name="className">The name of the associated class that creates an instance of a panel</param>
/// <param name="elementName">The name of a panel element</param>
/// <returns>Set of panels / a panel / the value of an element in a panel</returns>
public object? GetPanelValue(string? className = null, string? elementName = null)
{
object panels;
if (Values.TryGetValue(nameof(panels), out panels))
{
if (panels is Dictionary<string, object> panelsDictionary)
{
if (className == null)
return panels;
if (panelsDictionary.TryGetValue(className, out object? panel))
{
if (elementName == null)
return panel;
GetElementValue(panel, elementName);
}
}
}
else if (panels != null)
{
if (className == null)
return panels;
var panel = GetElementValue(panels, className);
if (elementName == null)
return panel;
GetElementValue(panel, elementName);
}
return null;
}
/// <summary>
/// Gets a panel of a specific type from the session
/// </summary>
/// <param name="type">Specific type</param>
/// <returns>Instance of the class specified with type, for the current session</returns>
public object? GetPanel(Type type)
{
object panels;
if (Values.TryGetValue(nameof(panels), out panels))
{
if (panels is Dictionary<string, object> panelsDictionary)
{
foreach (var panel in panelsDictionary)
{
if (panel.Value.GetType() == type)
return panel.Value;
}
}
}
return null;
}
private void OnSessionExpired()
{
object? classInfoList;
if (Values.TryGetValue(nameof(classInfoList), out classInfoList))
{
if (classInfoList is List<ClassInfo> panelsList)
{
foreach (var panel in panelsList)
{
if (!panel.IsStatic)
panel.IsExpired = true; // Mark the panel as expired
}
}
}
}
/// <summary>
/// Gets ClassInfo by its ID
/// </summary>
/// <param name="id"></param>
/// <returns>ClassInfo object</returns>
public ClassInfo? GetClassInfoById(Guid id)
{
object? classInfoList;
if (Values.TryGetValue(nameof(classInfoList), out classInfoList))
{
if (classInfoList is List<ClassInfo> panelsList)
{
foreach (var panel in panelsList)
{
if (panel.Id == id)
return panel;
}
}
}
return null;
}
/// <summary>
/// Gets a property or field value from an object by name using reflection
/// </summary>
/// <param name="obj">Object to inspect</param>
/// <param name="elementName">Property or field name</param>
/// <returns>Value of the property or field if found, null otherwise</returns>
private static object? GetElementValue(object obj, string elementName)
{
PropertyInfo proprieta = obj.GetType().GetProperty(elementName);
if (proprieta != null)
{
return proprieta.GetValue(obj);
}
FieldInfo campo = obj.GetType().GetField(elementName);
if (campo != null)
{
return campo.GetValue(obj);
}
return null;
}
/// <summary>
/// Creates a new session with the specified GUID
/// </summary>
/// <param name="id">Session GUID</param>
public Session(Guid id)
{
Id = id;
ExpireSessionTimer = new Timer(OnExpired, null, Sessions.TimeOut, Sessions.TimeOut);
lock (Sessions.ActiveSessions)
{
Sessions.ActiveSessions[Id] = this;
}
}
private readonly Guid Id;
/// <summary>
/// Refreshes the session timeout
/// </summary>
public void Refresh()
{
ExpireSessionTimer.Change(Sessions.TimeOut, Sessions.TimeOut);
}
Timer ExpireSessionTimer;
/// <summary>
/// Handles session expiration by cleaning up resources and removing from active sessions
/// </summary>
private void OnExpired(object? obj)
{
Sessions.Expired(this);
lock (Sessions.ActiveSessions)
{
if (Sessions.ActiveSessions.TryGetValue(Id, out Session? value))
{
value.OnSessionExpired();
Sessions.ActiveSessions.Remove(Id);
}
}
ExpireSessionTimer.Change(Timeout.Infinite, Timeout.Infinite);
ExpireSessionTimer.Dispose();
}
/// <summary>
/// Dictionary storing session values
/// </summary>
public Dictionary<string, object> Values = [];
/// <summary>
/// Used to get and set values associated with the current session.
/// </summary>
public dynamic Val
{
get
{
return new DynamicWrapper(Values);
}
set
{
if (value is ExpandoObject expando)
{
var expandoDict = (IDictionary<string, object>)expando;
foreach (var kvp in expandoDict)
{
Values[kvp.Key] = kvp.Value;
}
}
else
{
throw new InvalidOperationException("Val setter accepts only ExpandoObject.");
}
}
}
private class DynamicWrapper : DynamicObject
{
private readonly IDictionary<string, object> _dictionary;
public DynamicWrapper(IDictionary<string, object> dictionary)
{
_dictionary = dictionary;
}
public override bool TryGetMember(GetMemberBinder binder, out object? result)
{
lock (_dictionary)
{
if (_dictionary.TryGetValue(binder.Name, out result))
{
return true;
}
}
result = null;
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object? value)
{
lock (_dictionary)
{
_dictionary[binder.Name] = value!;
}
return true;
}
}
}
/// <summary>
/// Middleware for managing sessions in the ASP.NET Core pipeline
/// </summary>
public class SessionMiddleware
{
private readonly RequestDelegate _next;
/// <summary>
/// Initializes the session middleware
/// </summary>
/// <param name="next">Next middleware in the pipeline</param>
public SessionMiddleware(RequestDelegate next)
{
_next = next;
}
/// <summary>
/// Invokes the middleware to refresh session timeout and continue pipeline execution
/// </summary>
/// <param name="context">Current HttpContext</param>
public async Task InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context)
{
Session.Sessions.RefreshSessionTimeOut(context);
await _next(context);
}
}
}