Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data

This commit is contained in:
2026-01-14 22:11:11 +01:00
parent 40a8431464
commit 3f7122d30a
350 changed files with 41444 additions and 119 deletions

View File

@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
namespace EmbedIO.Sessions
{
/// <summary>
/// Represents a session.
/// </summary>
public interface ISession
{
/// <summary>
/// A unique identifier for the session.
/// </summary>
/// <value>The unique identifier for this session.</value>
/// <seealso cref="Session.IdComparison"/>
/// <seealso cref="Session.IdComparer"/>
string Id { get; }
/// <summary>
/// Gets the time interval, starting from <see cref="LastActivity"/>,
/// after which the session expires.
/// </summary>
/// <value> The expiration time.</value>
TimeSpan Duration { get; }
/// <summary>
/// Gets the UTC date and time of last activity on the session.
/// </summary>
/// <value>
/// The UTC date and time of last activity on the session.
/// </value>
DateTime LastActivity { get; }
/// <summary>
/// Gets the number of key/value pairs contained in a session.
/// </summary>
/// <value>
/// The number of key/value pairs contained in the object that implements <see cref="ISession"/>.
/// </value>
int Count { get; }
/// <summary>
/// Gets a value that indicates whether a session is empty.
/// </summary>
/// <value>
/// <see langword="true"/> if the object that implements <see cref="ISession"/> is empty,
/// i.e. contains no key / value pairs; otherwise, <see langword="false"/>.
/// </value>
bool IsEmpty { get; }
/// <summary>
/// <para>Gets or sets the value associated with the specified key.</para>
/// <para>Note that a session does not store null values; therefore, setting this property to <see langword="null"/>
/// has the same effect as removing <paramref name="key"/> from the dictionary.</para>
/// </summary>
/// <value>
/// The value associated with the specified key, if <paramref name="key"/>
/// is found in the dictionary; otherwise, <see langword="null"/>.
/// </value>
/// <param name="key">The key of the value to get or set.</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
object this[string key] { get; set; }
/// <summary>
/// Removes all keys and values from a session.
/// </summary>
void Clear();
/// <summary>
/// Determines whether a session contains an element with the specified key.
/// </summary>
/// <param name="key">The key to locate in the object that implements <see cref="ISession"/>.</param>
/// <returns>
/// <see langword="true"/> if the object that implements <see cref="ISession"/> contains an element with the key;
/// otherwise, <see langword="false"/> .
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
bool ContainsKey(string key);
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">The key whose value to get.</param>
/// <param name="value">When this method returns, the value associated with the specified <paramref name="key"/>,
/// if the key is found; otherwise, <see langword="null"/>. This parameter is passed uninitialized.</param>
/// <returns><see langword="true"/> if the object that implements <see cref="ISession"/>
/// contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
bool TryGetValue(string key, out object value);
/// <summary>
/// Attempts to remove and return the value that has the specified key from a session.
/// </summary>
/// <param name="key">The key of the element to remove and return.</param>
/// <param name="value">When this method returns, the value removed from the object that implements <see cref="ISession"/>,
/// if the key is found; otherwise, <see langword="null"/>. This parameter is passed uninitialized.</param>
/// <returns><see langword="true"/> if the value was removed successfully; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
bool TryRemove(string key, out object value);
/// <summary>
/// Takes and returns a snapshot of the contents of a session at the time of calling.
/// </summary>
/// <returns>An <see cref="IReadOnlyList{T}">IReadOnlyList&lt;KeyValuePair&lt;string,object&gt;&gt;</see> interface
/// containing an immutable copy of the session data as it was at the time of calling this method.</returns>
/// <remarks>
/// <para>The objects contained in the session data are copied by reference, not cloned; therefore
/// you should be aware that their state may change even after the snapshot is taken.</para>
/// </remarks>
IReadOnlyList<KeyValuePair<string, object>> TakeSnapshot();
}
}

View File

@@ -0,0 +1,46 @@
using System.Threading;
namespace EmbedIO.Sessions
{
/// <summary>
/// Represents a session manager, which is in charge of managing session objects
/// and their association to HTTP contexts.
/// </summary>
public interface ISessionManager
{
/// <summary>
/// Signals a session manager that the web server is starting.
/// </summary>
/// <param name="cancellationToken">The cancellation token used to stop the web server.</param>
void Start(CancellationToken cancellationToken);
/// <summary>
/// Returns the session associated with an <see cref="IHttpContext"/>.
/// If a session ID can be retrieved for the context and stored session data
/// are available, the returned <see cref="ISession"/> will contain those data;
/// otherwise, a new session is created and its ID is stored in the response
/// to be retrieved by subsequent requests.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>An <see cref="ISession"/> interface.</returns>
ISession Create(IHttpContext context);
/// <summary>
/// Deletes the session (if any) associated with the specified context
/// and removes the session's ID from the context.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <param name="id">The unique ID of the session.</param>
/// <seealso cref="ISession.Id"/>
void Delete(IHttpContext context, string id);
/// <summary>
/// <para>Called by a session proxy when a session has been obtained
/// for an <see cref="IHttpContext"/> and the context is closed,
/// even if the session was subsequently deleted.</para>
/// <para>This method can be used to save session data to a storage medium.</para>
/// </summary>
/// <param name="context">The <see cref="IHttpContext"/> for which a session was obtained.</param>
void OnContextClose(IHttpContext context);
}
}

View File

@@ -0,0 +1,33 @@
namespace EmbedIO.Sessions
{
/// <summary>
/// Represents a session proxy, i.e. an object that provides
/// the same interface as a session object, plus a basic interface
/// to a session manager.
/// </summary>
/// <remarks>
/// A session proxy can be used just as if it were a session object.
/// A session is automatically created wherever its data are accessed.
/// </remarks>
/// <seealso cref="ISession" />
public interface ISessionProxy : ISession
{
/// <summary>
/// Gets a value indicating whether a session exists for the current context.
/// </summary>
/// <value>
/// <see langword="true"/> if a session exists; otherwise, <see langword="false"/>.
/// </value>
bool Exists { get; }
/// <summary>
/// Deletes the session for the current context.
/// </summary>
void Delete();
/// <summary>
/// Deletes the session for the current context and creates a new one.
/// </summary>
void Regenerate();
}
}

View File

@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
namespace EmbedIO.Sessions.Internal
{
internal sealed class DummySessionProxy : ISessionProxy
{
private DummySessionProxy()
{
}
public static ISessionProxy Instance { get; } = new DummySessionProxy();
public bool Exists => false;
/// <inheritdoc/>
public string Id => throw NoSessionManager();
/// <inheritdoc/>
public TimeSpan Duration => throw NoSessionManager();
/// <inheritdoc/>
public DateTime LastActivity => throw NoSessionManager();
/// <inheritdoc/>
public int Count => 0;
/// <inheritdoc/>
public bool IsEmpty => true;
/// <inheritdoc/>
public object this[string key]
{
get => throw NoSessionManager();
set => throw NoSessionManager();
}
/// <inheritdoc/>
public void Delete()
{
}
/// <inheritdoc/>
public void Regenerate() => throw NoSessionManager();
/// <inheritdoc/>
public void Clear()
{
}
/// <inheritdoc/>
public bool ContainsKey(string key) => throw NoSessionManager();
/// <inheritdoc/>
public bool TryGetValue(string key, out object value) => throw NoSessionManager();
/// <inheritdoc/>
public bool TryRemove(string key, out object value) => throw NoSessionManager();
/// <inheritdoc/>
public IReadOnlyList<KeyValuePair<string, object>> TakeSnapshot() => throw NoSessionManager();
private InvalidOperationException NoSessionManager() => new InvalidOperationException("No session manager registered in the web server.");
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Linq;
using EmbedIO.Utilities;
namespace EmbedIO.Sessions
{
partial class LocalSessionManager
{
private class SessionImpl : ISession
{
private readonly Dictionary<string, object> _data = new Dictionary<string, object>(Session.KeyComparer);
private int _usageCount;
public SessionImpl(string id, TimeSpan duration)
{
Id = Validate.NotNullOrEmpty(nameof(id), id);
Duration = duration;
LastActivity = DateTime.UtcNow;
_usageCount = 1;
}
public string Id { get; }
public TimeSpan Duration { get; }
public DateTime LastActivity { get; private set; }
public int Count
{
get
{
lock (_data)
{
return _data.Count;
}
}
}
public bool IsEmpty
{
get
{
lock (_data)
{
return _data.Count == 0;
}
}
}
public object? this[string key]
{
get
{
lock (_data)
{
return _data.TryGetValue(key, out var value) ? value : null;
}
}
set
{
lock (_data)
{
if (value == null)
_data.Remove(key);
else
_data[key] = value;
}
}
}
public void Clear()
{
lock (_data)
{
_data.Clear();
}
}
public bool ContainsKey(string key)
{
lock (_data)
{
return _data.ContainsKey(key);
}
}
public bool TryRemove(string key, out object value)
{
lock (_data)
{
if (!_data.TryGetValue(key, out value))
return false;
_data.Remove(key);
return true;
}
}
public IReadOnlyList<KeyValuePair<string, object>> TakeSnapshot()
{
lock (_data)
{
return _data.ToArray();
}
}
public bool TryGetValue(string key, out object value)
{
lock (_data)
{
return _data.TryGetValue(key, out value);
}
}
internal void BeginUse()
{
lock (_data)
{
_usageCount++;
LastActivity = DateTime.UtcNow;
}
}
internal void EndUse(Action unregister)
{
lock (_data)
{
--_usageCount;
UnregisterIfNeededCore(unregister);
}
}
internal void UnregisterIfNeeded(Action unregister)
{
lock (_data)
{
UnregisterIfNeededCore(unregister);
}
}
private void UnregisterIfNeededCore(Action unregister)
{
if (_usageCount < 1 && (IsEmpty || DateTime.UtcNow > LastActivity + Duration))
unregister();
}
}
}
}

View File

@@ -0,0 +1,303 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using EmbedIO.Utilities;
namespace EmbedIO.Sessions
{
/// <summary>
/// <para>A simple session manager to handle in-memory sessions.</para>
/// <para>Not for intensive use or for distributed applications.</para>
/// </summary>
public partial class LocalSessionManager : ISessionManager
{
/// <summary>
/// The default name for session cookies, i.e. <c>"__session"</c>.
/// </summary>
public const string DefaultCookieName = "__session";
/// <summary>
/// The default path for session cookies, i.e. <c>"/"</c>.
/// </summary>
public const string DefaultCookiePath = "/";
/// <summary>
/// The default HTTP-only flag for session cookies, i.e. <see langword="true"/>.
/// </summary>
public const bool DefaultCookieHttpOnly = true;
/// <summary>
/// The default duration for session cookies, i.e. <see cref="TimeSpan.Zero"/>.
/// </summary>
public static readonly TimeSpan DefaultCookieDuration = TimeSpan.Zero;
/// <summary>
/// The default duration for sessions, i.e. 30 minutes.
/// </summary>
public static readonly TimeSpan DefaultSessionDuration = TimeSpan.FromMinutes(30);
/// <summary>
/// The default interval between automatic purges of expired and empty sessions, i.e. 30 seconds.
/// </summary>
public static readonly TimeSpan DefaultPurgeInterval = TimeSpan.FromSeconds(30);
private readonly ConcurrentDictionary<string, SessionImpl> _sessions =
new ConcurrentDictionary<string, SessionImpl>(Session.KeyComparer);
private string _cookieName = DefaultCookieName;
private string _cookiePath = DefaultCookiePath;
private TimeSpan _cookieDuration = DefaultCookieDuration;
private bool _cookieHttpOnly = DefaultCookieHttpOnly;
private TimeSpan _sessionDuration = DefaultSessionDuration;
private TimeSpan _purgeInterval = DefaultPurgeInterval;
/// <summary>
/// Initializes a new instance of the <see cref="LocalSessionManager"/> class
/// with default values for all properties.
/// </summary>
/// <seealso cref="DefaultSessionDuration"/>
/// <seealso cref="DefaultPurgeInterval"/>
/// <seealso cref="DefaultCookieName"/>
/// <seealso cref="DefaultCookiePath"/>
/// <seealso cref="DefaultCookieDuration"/>
/// <seealso cref="DefaultCookieHttpOnly"/>
public LocalSessionManager()
{
}
/// <summary>
/// Gets or sets the duration of newly-created sessions.
/// </summary>
/// <exception cref="InvalidOperationException">This property is being set after calling
/// the <see cref="Start"/> method.</exception>
/// <seealso cref="DefaultSessionDuration"/>
public TimeSpan SessionDuration
{
get => _sessionDuration;
set
{
EnsureConfigurationNotLocked();
_sessionDuration = value;
}
}
/// <summary>
/// Gets or sets the interval between purges of expired sessions.
/// </summary>
/// <exception cref="InvalidOperationException">This property is being set after calling
/// the <see cref="Start"/> method.</exception>
/// <seealso cref="DefaultPurgeInterval"/>
public TimeSpan PurgeInterval
{
get => _purgeInterval;
set
{
EnsureConfigurationNotLocked();
_purgeInterval = value;
}
}
/// <summary>
/// <para>Gets or sets the name for session cookies.</para>
/// </summary>
/// <exception cref="InvalidOperationException">This property is being set after calling
/// the <see cref="Start"/> method.</exception>
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">This property is being set and the provided value
/// is not a valid URL path.</exception>
/// <seealso cref="DefaultCookieName"/>
public string CookieName
{
get => _cookieName;
set
{
EnsureConfigurationNotLocked();
_cookieName = Validate.Rfc2616Token(nameof(value), value);
}
}
/// <summary>
/// <para>Gets or sets the path for session cookies.</para>
/// </summary>
/// <exception cref="InvalidOperationException">This property is being set after calling
/// the <see cref="Start"/> method.</exception>
/// <exception cref="ArgumentNullException">This property is being set to <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">This property is being set and the provided value
/// is not a valid URL path.</exception>
/// <seealso cref="DefaultCookiePath"/>
public string CookiePath
{
get => _cookiePath;
set
{
EnsureConfigurationNotLocked();
_cookiePath = Validate.UrlPath(nameof(value), value, true);
}
}
/// <summary>
/// Gets or sets the duration of session cookies.
/// </summary>
/// <exception cref="InvalidOperationException">This property is being set after calling
/// the <see cref="Start"/> method.</exception>
/// <seealso cref="DefaultCookieDuration"/>
public TimeSpan CookieDuration
{
get => _cookieDuration;
set
{
EnsureConfigurationNotLocked();
_cookieDuration = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether session cookies are hidden from Javascript code running on a user agent.
/// </summary>
/// <exception cref="InvalidOperationException">This property is being set after calling
/// the <see cref="Start"/> method.</exception>
/// <seealso cref="DefaultCookieHttpOnly"/>
public bool CookieHttpOnly
{
get => _cookieHttpOnly;
set
{
EnsureConfigurationNotLocked();
_cookieHttpOnly = value;
}
}
private bool ConfigurationLocked { get; set; }
/// <inheritdoc />
public void Start(CancellationToken cancellationToken)
{
ConfigurationLocked = true;
Task.Run(async () =>
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
PurgeExpiredAndEmptySessions();
await Task.Delay(PurgeInterval, cancellationToken).ConfigureAwait(false);
}
}
catch (TaskCanceledException)
{
// ignore
}
}, cancellationToken);
}
/// <inheritdoc />
public ISession Create(IHttpContext context)
{
var id = context.Request.Cookies.FirstOrDefault(IsSessionCookie)?.Value.Trim();
SessionImpl session;
lock (_sessions)
{
if (!string.IsNullOrEmpty(id) && _sessions.TryGetValue(id!, out session))
{
session.BeginUse();
}
else
{
id = UniqueIdGenerator.GetNext();
session = new SessionImpl(id, SessionDuration);
_sessions.TryAdd(id, session);
}
}
context.Request.Cookies.Add(BuildSessionCookie(id));
context.Response.Cookies.Add(BuildSessionCookie(id));
return session;
}
/// <inheritdoc />
public void Delete(IHttpContext context, string id)
{
lock (_sessions)
{
if (_sessions.TryGetValue(id, out var session))
session.EndUse(() => _sessions.TryRemove(id, out _));
}
context.Request.Cookies.Add(BuildSessionCookie(string.Empty));
context.Response.Cookies.Add(BuildSessionCookie(string.Empty));
}
/// <inheritdoc />
public void OnContextClose(IHttpContext context)
{
if (!context.Session.Exists)
return;
var id = context.Session.Id;
lock (_sessions)
{
if (_sessions.TryGetValue(id, out var session))
{
session.EndUse(() => _sessions.TryRemove(id, out _));
}
}
}
private void EnsureConfigurationNotLocked()
{
if (ConfigurationLocked)
throw new InvalidOperationException($"Cannot configure a {nameof(LocalSessionManager)} once it has been started.");
}
private bool IsSessionCookie(Cookie cookie)
=> cookie.Name.Equals(CookieName, StringComparison.OrdinalIgnoreCase)
&& !cookie.Expired;
private Cookie BuildSessionCookie(string? id)
{
var cookie = new Cookie(CookieName, id, CookiePath)
{
HttpOnly = CookieHttpOnly,
};
if (CookieDuration > TimeSpan.Zero)
{
cookie.Expires = DateTime.UtcNow.Add(CookieDuration);
}
return cookie;
}
private void PurgeExpiredAndEmptySessions()
{
string[] ids;
lock (_sessions)
{
ids = _sessions.Keys.ToArray();
}
foreach (var id in ids)
{
lock (_sessions)
{
if (!_sessions.TryGetValue(id, out var session))
return;
session.UnregisterIfNeeded(() => _sessions.TryRemove(id, out _));
}
}
}
private string GetSessionId(IHttpContext context) => context.Request.Cookies.FirstOrDefault(IsSessionCookie)?.Value.Trim() ?? string.Empty;
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
namespace EmbedIO.Sessions
{
/// <summary>
/// Provides useful constants related to session management.
/// </summary>
public static class Session
{
/// <summary>
/// <para>The <seealso cref="StringComparison"/> used to disambiguate session IDs.</para>
/// <para>Corresponds to <see cref="StringComparison.Ordinal"/>.</para>
/// </summary>
public const StringComparison IdComparison = StringComparison.Ordinal;
/// <summary>
/// <para>The <seealso cref="StringComparison"/> used to disambiguate session keys.</para>
/// <para>Corresponds to <see cref="StringComparison.InvariantCulture"/>.</para>
/// </summary>
public const StringComparison KeyComparison = StringComparison.InvariantCulture;
/// <summary>
/// <para>The equality comparer used for session IDs.</para>
/// <para>Corresponds to <see cref="StringComparer.Ordinal"/>.</para>
/// </summary>
public static readonly IEqualityComparer<string> IdComparer = StringComparer.Ordinal;
/// <summary>
/// <para>The equality comparer used for session keys.</para>
/// <para>Corresponds to <see cref="StringComparer.InvariantCulture"/>.</para>
/// </summary>
public static readonly IEqualityComparer<string> KeyComparer = StringComparer.InvariantCulture;
}
}

View File

@@ -0,0 +1,60 @@
using System;
namespace EmbedIO.Sessions
{
/// <summary>
/// Provides extension methods for types implementing <see cref="ISession"/>.
/// </summary>
public static class SessionExtensions
{
/// <summary>Gets the value associated with the specified key.</summary>
/// <typeparam name="T">The desired type of the value.</typeparam>
/// <param name="this">The <see cref="ISession"/> on which this method is called.</param>
/// <param name="key">The key whose value to get from the session.</param>
/// <param name="value">
/// <para>When this method returns, the value associated with the specified key,
/// if the key is found and the associated value is of type <typeparamref name="T"/>;
/// otherwise, the default value for <typeparamref name="T"/>.</para>
/// <para>This parameter is passed uninitialized.</para>
/// </param>
/// <returns><see langword="true"/> if the key is found and the associated value is of type <typeparamref name="T"/>;
/// otherwise, <see langword="false"/>.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
public static bool TryGetValue<T>(this ISession @this, string key, out T value)
{
if (@this.TryGetValue(key, out var foundValue) && foundValue is T typedValue)
{
value = typedValue;
return true;
}
#pragma warning disable CS8653 // "default" can be null - We are returning false, so value is undefined
value = default;
#pragma warning restore CS8653
return false;
}
/// <summary>Gets the value associated with the specified key.</summary>
/// <typeparam name="T">The desired type of the value.</typeparam>
/// <param name="this">The <see cref="ISession"/> on which this method is called.</param>
/// <param name="key">The key whose value to get from the session.</param>
/// <returns>The value associated with the specified key,
/// if the key is found and the associated value is of type <typeparamref name="T"/>;
/// otherwise, the default value for <typeparamref name="T"/>.</returns>
public static T GetValue<T>(this ISession @this, string key)
=> @this.TryGetValue(key, out var value) && value is T typedValue ? typedValue : default;
/// <summary>Gets the value associated with the specified key.</summary>
/// <typeparam name="T">The desired type of the value.</typeparam>
/// <param name="this">The <see cref="ISession"/> on which this method is called.</param>
/// <param name="key">The key whose value to get from the session.</param>
/// <param name="defaultValue">The default value to return if the key is not found
/// or its associated value is not of type <typeparamref name="T"/>.</param>
/// <returns>The value associated with the specified key,
/// if the key is found and the associated value is of type <typeparamref name="T"/>;
/// otherwise, <paramref name="defaultValue"/>.</returns>
public static T GetOrDefault<T>(this ISession @this, string key, T defaultValue)
=> @this.TryGetValue(key, out var value) && value is T typedValue ? typedValue : defaultValue;
}
}

View File

@@ -0,0 +1,167 @@
using System;
using System.Collections.Generic;
using EmbedIO.Sessions.Internal;
namespace EmbedIO.Sessions
{
/// <summary>
/// Provides the same interface as a session object,
/// plus a basic interface to a session manager.
/// </summary>
/// <remarks>
/// A session proxy can be used just as if it were a session object.
/// A session is automatically created wherever its data are accessed.
/// </remarks>
/// <seealso cref="ISessionProxy" />
public sealed class SessionProxy : ISessionProxy
{
private readonly IHttpContext _context;
private readonly ISessionManager? _sessionManager;
private ISession? _session;
private bool _onCloseRegistered;
internal SessionProxy(IHttpContext context, ISessionManager? sessionManager)
{
_context = context;
_sessionManager = sessionManager;
}
/// <summary>
/// Returns a "dummy" <see cref="ISessionProxy"/> interface that will always behave as if no session manager has been defined.
/// </summary>
/// <remarks>
/// <para>The returned <see cref="ISessionProxy"/> interface is only useful
/// to initialize a non-nullable property of type <see cref="ISessionProxy"/>.</para>
/// </remarks>
public static ISessionProxy None => DummySessionProxy.Instance;
/// <inheritdoc/>
public bool Exists => _session != null;
/// <inheritdoc/>
public string Id
{
get
{
EnsureSessionExists();
return _session!.Id;
}
}
/// <inheritdoc/>
public TimeSpan Duration
{
get
{
EnsureSessionExists();
return _session!.Duration;
}
}
/// <inheritdoc/>
public DateTime LastActivity
{
get
{
EnsureSessionExists();
return _session!.LastActivity;
}
}
/// <inheritdoc/>
public int Count => _session?.Count ?? 0;
/// <inheritdoc/>
public bool IsEmpty => _session?.IsEmpty ?? true;
/// <inheritdoc/>
public object this[string key]
{
get
{
EnsureSessionExists();
return _session![key];
}
set
{
EnsureSessionExists();
_session![key] = value;
}
}
/// <inheritdoc/>
public void Delete()
{
EnsureSessionExists();
if (_session == null)
return;
_sessionManager!.Delete(_context, _session.Id);
_session = null;
}
/// <inheritdoc/>
public void Regenerate()
{
if (_session != null)
_sessionManager!.Delete(_context, _session.Id);
EnsureSessionManagerExists();
_session = _sessionManager!.Create(_context);
}
/// <inheritdoc/>
public void Clear() => _session?.Clear();
/// <inheritdoc/>
public bool ContainsKey(string key)
{
EnsureSessionExists();
return _session!.ContainsKey(key);
}
/// <inheritdoc/>
public bool TryGetValue(string key, out object value)
{
EnsureSessionExists();
return _session!.TryGetValue(key, out value);
}
/// <inheritdoc/>
public bool TryRemove(string key, out object value)
{
EnsureSessionExists();
return _session!.TryRemove(key, out value);
}
/// <inheritdoc/>
public IReadOnlyList<KeyValuePair<string, object>> TakeSnapshot()
{
EnsureSessionExists();
return _session!.TakeSnapshot();
}
private void EnsureSessionManagerExists()
{
if (_sessionManager == null)
throw new InvalidOperationException("No session manager registered in the web server.");
}
private void EnsureSessionExists()
{
if (_session != null)
return;
EnsureSessionManagerExists();
_session = _sessionManager!.Create(_context);
if (_onCloseRegistered)
return;
_context.OnClose(_sessionManager.OnContextClose);
_onCloseRegistered = true;
}
}
}