ASP.NET 5 (Core): How to store objects in session-cache (ISession)?
Asked Answered
H

3

25

I am writing an ASP.NET 5 MVC 6 (Core) application. Now I came to a point where I need to store (set and get) an object in the session-cache (ISession).

As you may know, the Set-method of ISession takes a byte-array and the Get-method returns one.

In a non-core-application I would use the BinaryFormatter to convert my object. But how can I do it in a core-application?

Hypozeuxis answered 14/3, 2016 at 16:49 Comment(0)
I
47

I'd go with serializing the objects to JSON and use the extensions methods on ISession to save them as string's.

// Save
var key = "my-key";
var str = JsonConvert.SerializeObject(obj);
context.Session.SetString(key, str);

// Retrieve
var str = context.Session.GetString(key);
var obj = JsonConvert.DeserializeObject<MyType>(str);

The extension methods on ISession are defined in the Microsoft.AspNet(Core).Http namespace.

Igraine answered 14/3, 2016 at 16:56 Comment(6)
Thank you for your answer. Is converting to Json for each session-call (could be nearly each request) performant? Should I take this as a clean solution or as a best-way-available-solution?Hypozeuxis
You can read this discussion about binary serialization on dotnet github repo, and why it will no be supported.Kaleykaleyard
@Hypozeuxis my guess is that it's far more performant than binary (de)serialization on every request. You might consider using protobuf.net which is a binary serializer and very fast.Igraine
Thanks for getting me on the right tracks! Your input was very helpful!Hypozeuxis
I validated your "guess" that the json-converting is more performant than binary-serialize. You were totally right. While for large objects, json-(de)serialize was only a little bit faster, for small objects it took only half of the time the binary-(de)serialize took.Hypozeuxis
How to associate a memory cache with a session and get that cache destroyed on session destroy?Anta
S
4

This is an evergreen post and the problem is still fresh, and even though Microsoft has recommended serialisation to store the object in session - it is not a correct solution unless your object is readonly, I have a blog explaining all scenario here and i have even pointed out the issues in GitHub of Asp.Net Core in issue id 18159

Synopsis of the problems are here:

A. Serialisation isn't same as object, true it will help in distributed server scenario but it comes with a caveat that Microsoft have failed to highlight - that it will work without any unpredictable failures only when the object is meant to be read and not to be written back.

B. If you were looking for a read-write object in the session, everytime you change the object that is read from the session after deserialisation - it needs to be written back to the session again by calling serialisation - and this alone can lead to multiple complexities as you will need to either keep track of the changes - or keep writing back to session after each change in any property. In one request to the server, you will have scenarios where the object is written back multiple times till the response is sent back.

C. For a read-write object in the session, even on a single server it will fail, as the actions of the user can trigger multiple rapid requests to the server and not more than often system will find itself in a situation where the object is being serialised or deserialised by one thread and being edited and then written back by another, the result is you will end up with overwriting the object state by threads - and even locks won't help you much since the object is not a real object but a temporary object created by deserialisation.

D. There are issues with serialising complex objects - it is not just a performance hit, it may even fail in certain scenario - especially if you have deeply nested objects that sometimes refer back to itself.

The synopsis of the solution is here, full implementation along with code is in the blog link:

  1. First implement this as a Cache object, create one item in IMemoryCache for each unique session.

  2. Keep the cache in sliding expiration mode, so that each time it is read it revives the expiry time - thereby keeping the objects in cache as long as the session is active.

  3. Second point alone is not enough, you will need to implement heartbeat technique - triggering the call to session every T minus 1 min or so from the javascript. (This we anyways used to do even to keep the session alive till the user is working on the browser, so it won't be any different

Additional Recommendations

A. Make an object called SessionManager - so that all your code related to session read / write sits in one place.

B. Do not keep very high value for session time out - If you are implementing heartbeat technique, even 3 mins of session time out will be enough.

Sn answered 11/1, 2020 at 7:59 Comment(6)
Thanks for the detailed explanation! So the message is: "Don't do this, except as a temporary solution"?Freudberg
correct....and for my case i had to do it since we are transitioning.....in our long term plan we are going to remove sessions completelySn
@Kalpesh Popat - What are you planning to use instead of sessions?Gosport
@Gosport at the moment we are storing user credentials and objects that are being edited in the session. Planning to fetch user credentials with each request and make the query efficient and scan only the permissions required for that specific request and also use caching to speed up. and i honestly haven't got a confirmed solution for the objects being edited - may be create a json file for each object, read it at the start of the request and write back at the end of the response. locks can be applied on the file to address concurrency. you have any suggestions ?Sn
@Kalpesh Popat - For now, we are using IDistributedCache and injecting it into our controllers. This still uses session cache, but it works a bit differently than if you use HttpContext.Session. The IDistributedCache writes out a separate row in your session state table in the DB for each key. If you use HttpContext.Session, it writes out a single row for all session state, which as you've noted, is a concurrency nightmare. Long term, we are considering using Azure Redis Cache, but for now, the IDistributedCache is a good enough solution.Gosport
Hi @Gosport the idea that you gave me was helpful and based on that I have implemented my own framework for distributed session cache on db it supports object store, plus can handle concurrency, much longer sessions and thanks to the distributed session now the application is also server farm ready.Sn
E
0

Another idea, use IMemoryCache and put the object under a key that includes the session id as well.

Evangelinaevangeline answered 5/6 at 23:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.