I'm developing an ASP.NET Core 8 application and using the singleton pattern for session management, but when I want to read the values, it displays null values!
Here you can see the code:
SessionSingleton.cs
:
[Serializable]
public sealed class SessionSingleton : IDisposable
{
#region Private Members
private const string SessionSingletonName = "SessionKey_502E69E5-668B-E011-951F-00155DF26207";
private static readonly object LockObject = new();
#endregion
#region Singleton
private SessionSingleton()
{
}
public static SessionSingleton Current
{
get
{
lock (LockObject)
{
string? value;
SessionSingleton sessionSingleton;
var session = CustomHttpContext.Current.Session;
if (session == null)
return null;
if (session.GetString(SessionSingletonName) is not null)
{
value = session.GetString(SessionSingletonName);
sessionSingleton = JsonSerializer.Deserialize<SessionSingleton>(value!)!;
}
else
{
sessionSingleton = new SessionSingleton();
var sessionText = JsonSerializer.Serialize(sessionSingleton);
session.SetString(SessionSingletonName, sessionText);
}
return sessionSingleton;
}
}
}
#endregion
#region Public properties
public string Name { get; set; }
#endregion
#region Dctor
public void Dispose()
{
// HttpContext.Current.Session[SessionSingletonName] = null;
// HttpContext.Current.Session.Remove(SessionSingletonName);
}
#endregion
}
CustomHttpContext
:
public static class CustomHttpContext
{
private static IHttpContextAccessor _httpContextAccessor = new HttpContextAccessor();
public static HttpContext? Current
{
get
{
if (_httpContextAccessor == null)
_httpContextAccessor = new HttpContextAccessor();
return _httpContextAccessor.HttpContext;
}
}
}
HomeController
:
public partial class HomeController : BaseController
{
public IActionResult Index()
{
SessionSingleton.Current.Name = "Test";
var value = SessionSingleton.Current.Name; // value is always null
SessionSingleton.Current.Name = "Test2";
value = SessionSingleton.Current.Name;// value is always null
return Ok(true);
}
}
All code like serialization and deserialization works fine, but why is value always null in HomeController
?
Please suggest a way to store the user's information and to be able to access that information simply during the execution of the program by the user.
Finally, after various checks, I changed my code as below and it solved my need well.
After many investigations, I realized that for any reason, using the combination of Singleton with HttpContextAccessor
will not lead to the desired result.(Maybe because of the logic of asp.net internal middlewares)
For this reason, I tried to meet my needs in a way that is both clean and extensible code.
Therefore, I used the interface and put my fields in it so that I can easily implement it and also use the dependency injection feature.
ISessionHandler
public interface ISessionHandler : IDisposable
{
public int UserId { get; set; }
public string Name { get; set; }
}
SessionManager
internal sealed class SessionManager : ISessionHandler
{
#region Private Members
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ISession _session;
#endregion
#region Ctor
public SessionManager(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_session = _httpContextAccessor.HttpContext?.Session!;
}
#endregion
#region Dctor
public void Dispose()
{
_session.Clear();
}
#endregion
public int UserId
{
get => _session.Get<int>();
set => _session.Set(value);
}
public string Name
{
get => _session.Get<string>();
set => _session.Set(value);
}
}
Then I created a static helper class to set and get information of the session.
SessionExtension
internal static class SessionExtension
{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default! : JsonSerializer.Deserialize<T>(value)!;
}
public static void Set<T>(this ISession session, T value)
{
var key = GetKey();
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T Get<T>(this ISession session)
{
var key = GetKey();
var value = session.GetString(key);
return value == null ? default! : JsonSerializer.Deserialize<T>(value)!;
}
private static string GetKey()
{
var frame = new StackTrace().GetFrame(2);
var instanceName = frame?.GetMethod()?.DeclaringType?.Name;
var methodName = frame?.GetMethod()?.Name;
var key = $"{instanceName}_{methodName}";
return key.Replace("_set", "").Replace("_get", "");
}
}
program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<ISessionHandler, SessionManager>();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
options.Cookie.Name = ".Test.Session";
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.IsEssential = true;
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseSession();
app.UseRequestLocalization();
app.MapDefaultControllerRoute();
app.Run();
And finally, as shown in the code below, I used it in my controller and wherever needed.
LoginController
public partial class LoginController : BaseController
{
public LoginController(ISessionHandler sessionHandler)
{
}
public IActionResult Index()
{
SessionHandler.Name = "Test";
var name = SessionHandler.Name; // It's OK
return Ok();
}
}