Storing C# objects in ASP.NET8 Session is supported by the framework; only strings, presumably JSON, can be stored in the Session. We show here code that extends ASP.NET8 framework and adds that functionality.
1. Saving C# objects not supported by ASP.NET8 Session
A quick look at ISession interface [2] of ASP.NET8 framework will tell us immediately that saving objects is not supported.
//Assembly Microsoft.AspNetCore.Http.Features, Version=8.0.0.0
namespace Microsoft.AspNetCore.Http
{
// The session data is backed by a cache and considered ephemeral data.
public interface ISession
{
bool IsAvailable { get; }
string Id { get; }
IEnumerable<string> Keys { get; }
void Clear();
Task CommitAsync(CancellationToken cancellationToken = default);
Task LoadAsync(CancellationToken cancellationToken = default);
void Remove(string key);
void Set(string key, byte[] value);
bool TryGetValue(string key, [NotNullWhen(true)] out byte[]? value);
}
}
What they envisioned is that the developer will save only serialized JSON objects.
1.1 Why is that a problem
There are 2 reasons why that is a problem:
- Migration of old code from ASP.NET from .NET4.8 is unnecessarily difficult, since that framework supports saving objects into the Session
- If you have a case of frequent writes into the session object, the process of serializing/deserializing into/from JSON consumes resources and complicates code
1.2 Sample Use Case
My use case was that I would create C# object ProgressLevel object in which I would keep the current state of completion, start long background processing, and have an AJAX method every 2 seconds read the completion state and show it to the user in the browser. That C# object ProgressLevel has 100+ writes during processing, so I did not want to deserialize it into/from JSON 100+ times. It needs to be serialized into JSON only every 2 seconds.
2. Solution is to extend ASP.NET Session functionality
I found an excellent solution to the problem in article [1] by the author Kalpesh. My solution/code here is a modification of that code adopted for my environment.
The idea is simple. Since Session itself is Cache based, why not extend it with ability to add C# objects to it?
System.Runtime.Caching.MemoryCache is the standard implementation of an in-memory cache for C# applications. So, let’s use System.Runtime.Caching.MemoryCache for that.
We are intentionally adding mutable objects to the Cache, but we have full control of them. They are not intended to be used as Cache, but as an objects store. Be careful about potential problems as described in [5].
Based on [4], System.Runtime.Caching.MemoryCache is thread-safe.
3. Code of Session extension class
Here is the code of SessionExtensions C# class. It is well commented, and it should be self-explanatory to anyone who can read C# code. If you can not read it, this might not be an article for you, please go read some other article, I recommend cnn.com .
public static class SessionExtensions
{
//Mark Pelf, May 2025
//code inspired with solution from
//http://blogs.sarasennovations.com/2020/01/storing-objects-in-session-in-aspnet.html
//adopted to my needs
private static ObjectCache _Cache;
//we just need some unique string to identify the session cache
private const string SESSION_CACHE_KEY = "_MyAppUniqueString_";
static SessionExtensions()
{
//MemoryCache is the standard implementation of an in-memory cache for C# applications.
//We are intentionally adding mutable objects to the Cache, but we have full control of them
//System.Runtime.Caching.MemoryCache is thread safe
//------------------------------
//parameter "sessionCache" to constructor is the name to use to look up
//configuration information.
//---------------------------------
//If a matching configuration entry exists, the configuration
//information is used to configure the MemoryCache instance.
_Cache = new System.Runtime.Caching.MemoryCache("sessionCache");
}
private static Dictionary<string, object>? GetPrivateObjectStore(string cacheKey)
{
CacheItem cacheItemForCacheKey = _Cache.GetCacheItem(cacheKey);
if (cacheItemForCacheKey != null)
{
return (Dictionary<string, object>)cacheItemForCacheKey.Value;
}
return null;
}
//this constructor will create a new object store
//it will overwrite any existing object store with same cacheKeyForThisSession
//it can NOT be called multiple times, because it will overwrite the previous store
//that is why I made it private
private static void InitObjectStore(this ISession session, double SessionTimeoutMin=0)
{
CacheItemPolicy cacheItemPolicyForThisSession = new CacheItemPolicy();
//decide if user wants to set a timeout(sliding expiration) for the
//objects he/she is storing in the session
if (SessionTimeoutMin > 0)
{
//set sliding timer to session time + 1, so that cache do not expire before session does
//objects will be removed if not used after timeout
cacheItemPolicyForThisSession.SlidingExpiration =
TimeSpan.FromMinutes(SessionTimeoutMin + 1);
}
else
{
//Keep objects until manually removed
cacheItemPolicyForThisSession.SlidingExpiration =
ObjectCache.NoSlidingExpiration;
}
string cacheKeyForThisSession = session.Id + SESSION_CACHE_KEY;
CacheItem cacheItemForThisSession = new CacheItem(cacheKeyForThisSession);
Dictionary<string, object> sessionObjectsStore = new Dictionary<string, object>();
cacheItemForThisSession.Value = sessionObjectsStore;
_Cache.Set(cacheItemForThisSession, cacheItemPolicyForThisSession);
}
//this constructor will check if the object store is already initialized
//if not, it will initialize it
//it can be called multiple times, no harm done
//I add this method to ASP.NET MVC Controller constructor
public static void InitObjectStoreIfNeeded(this ISession session, double SessionTimeoutMin = 0)
{
string cacheKeyForThisSession = session.Id + SESSION_CACHE_KEY;
CacheItem cacheItemForThisSession = _Cache.GetCacheItem(cacheKeyForThisSession);
if (cacheItemForThisSession == null)
{
InitObjectStore(session, SessionTimeoutMin);
}
}
public static void RemoveObjectStore(this ISession session)
{
string cacheKeyForThisSession = session.Id + SESSION_CACHE_KEY;
Dictionary<string, object>? sessionObjectsStore = GetPrivateObjectStore(cacheKeyForThisSession);
if(sessionObjectsStore != null)
{
sessionObjectsStore.Clear();
//also remove the collection from cache
_Cache.Remove(cacheKeyForThisSession);
}
}
public static Dictionary<string, object>? GetAllObjectsKeyValuePair(this ISession session)
{
Dictionary<string, object>? sessionObjectsStore =
GetPrivateObjectStore(session.Id + SESSION_CACHE_KEY);
return sessionObjectsStore;
}
public static void RemoveObject(this ISession session, string key_StringIdOfObject)
{
string cacheKeyForThisSession = session.Id + SESSION_CACHE_KEY;
Dictionary<string, object>? sessionObjectsStore =
GetPrivateObjectStore(cacheKeyForThisSession);
if (sessionObjectsStore != null)
{
sessionObjectsStore.Remove(key_StringIdOfObject);
}
}
public static void SetObject(this ISession session,
string key_StringIdOfObject, object? value_ReferenceToObject)
{
InitObjectStoreIfNeeded(session);
Dictionary<string, object>? sessionObjectsStore =
GetPrivateObjectStore(session.Id + SESSION_CACHE_KEY);
if (sessionObjectsStore == null)
throw new Exception("Object store is null after InitObjectStore call.");
if (value_ReferenceToObject == null )
{
sessionObjectsStore.Remove(key_StringIdOfObject);
}
else
{
sessionObjectsStore[key_StringIdOfObject] = value_ReferenceToObject;
}
}
public static object? GetObject(this ISession session, string key_StringIdOfObject)
{
string cacheKeyForThisSession = session.Id + SESSION_CACHE_KEY;
Dictionary<string, object>? sessionObjectsStore =
GetPrivateObjectStore(cacheKeyForThisSession);
object? result_ReferenceToObject = null;
if (sessionObjectsStore != null)
{
result_ReferenceToObject = sessionObjectsStore[key_StringIdOfObject];
}
return result_ReferenceToObject;
}
}
3.1 Usage
Here are a few examples of how I used the code above. It is well commented, and it should be self-explanatory.
//I added this to my ASP.NET8 MVC base controller class
//you can add this to your ASP.NET8 MVC Controller constructor
//it can be called multiple times, no harm done
{
//Initialize ObjectStore (session extension functions)
var CurrentSession = this.HttpContext?.Session;
if (CurrentSession != null)
{
CurrentSession.InitObjectStoreIfNeeded();
}
}
//==========================================================
//This is how I save object into the Session
const string ProgressLevel_ReleaseContract = "ProgressLevel_ReleaseContract";
ProgressLevel? Output1 = null;
Output1 = new ProgressLevel();
{
ISession? CurrentSession = this.HttpContext?.Session;
if (CurrentSession != null)
{
CurrentSession.SetObject(ProgressLevel_ReleaseContract, Output1);
}
}
//==========================================================
//Usage of object from Session
//Reference to C# object ProgressLevel? Output1 is passed as a parameter to
//different methods that do background processing and in that object they report
//on work/changes/progress done
//In my case, processing is "per ASO.NET session" and is quite serialized,
//and since C# object ProgressLevel? Output1 is unique per session, I do not have
//much parallelism/concurrency issues.
//Actually, the problem is when background thread is doing some processing ans writing
//into ProgressLevel object, and UI is doing AJAX and reading and JSON serializing
//same object, that is concurrency case, but works fine in my case.
//==========================================================
//This is how I get object from Session in my AJAX action
//and serialize it into JSon
public IActionResult ContractReleaseExecution_ProgressStatus_AJAX(string? notUsed=null)
{
string? jsonProgressLevel = null;
try
{
ISession? CurrentSession = this.HttpContext?.Session;
if (CurrentSession != null)
{
object? obj1 = CurrentSession.GetObject(ProgressLevel_ReleaseContract);
ProgressLevel? progressLevel = obj1 as ProgressLevel;
if (progressLevel != null)
{
jsonProgressLevel = JsonConvert.SerializeObject(progressLevel);
}
}
}
catch (Exception ex)
{
string methodName = $"Type: {System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType?.FullName}, " +
$"Method: ContractReleaseExecution_ProgressStatus_AJAX; ";
Log.Error(methodName, ex);
jsonProgressLevel = null;
}
return Json(jsonProgressLevel);
}
//==============================================
4. Conclusion
This solution to extend ASP.NET8 Session with the ability to save C# objects was very practical and useful to me. I think ASP.NET Core framework creators were a bit detached from reality and practical programming with their insisting/limitations of Session objects being only serialized JSON strings. In the past, there was a lot of fascination with XML format, then now with JSON for serialization. All these formats are useful in certain contexts, but do not cover all the use cases. Forcing restrictions to just any of those formats is a serious limitation.
5 References
[1] Storing Objects in In-Memory Session in Asp.Net Core
http://blogs.sarasennovations.com/2020/01/storing-objects-in-session-in-aspnet.html
[2] ISession Interface
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.isession?view=aspnetcore-9.0
[3] MemoryCache in C#: A Practical Guide
https://blog.postsharp.net/memorycache
[4] MemoryCache Class
https://learn.microsoft.com/en-us/dotnet/api/system.runtime.caching.memorycache?view=net-9.0-pp
[5] My own pitfalls with .NET runtime cache
https://www.enkelmedia.se/blogg/2021/10/13/my-own-pitfalls-with-net-runtime-cache