Think twice about using session state
So this question comes up all the time: “Where should I keep my shopping cart data for my application?” The common knee-jerk response is “use session state”. I find this to typically be the wrong answer if you want to build a resilient and scalable application.
Session state was originally meant for this kind of data (user provided data like a shopping cart). One thing session is not meant for is caching of user profile data. Back in classic ASP there were not a lot of other caching per-user data, so session was the obvious choice, but in ASP.NET (and just .NET in general) there are so many better options. One such example is the data cache in ASP.NET. With the data cache you specifically code to deal with failure (the cache item might not be present) and you can also set very specific conditions per cache item designating how long you’d like the cache the item. The data cache is very useful for a place to store data when you want to avoid round trips tot he database. But why not use session? Wouldn’t be essentially the same since it is also stored in-memory? Read on…
So back to session state as a place to store user driven data (i.e. shopping cart)… Session state is stored in-memory by default. This is a horrible option if you want to remember data for your users. In ASP.NET the hosting application pool frequently recycles and this is typically out of your control. This means that all that in-memory state is lost, which is why using session state in-proc is a bad choice. If you’re going to the effort to keep this data for the users, you simply can’t rely upon in-memory state — think shopping cart which equates to making sales on your website or simply the frustration of your users when all their data is lost and you have to make them start all of their shopping over again.
As an alternative you can store your session out-of-proc (in the ASP.NET State Service which is a windows service or in SQL Server or in a custom store). This certainly makes the state more resilient, but there is a cost to this resiliency. Each HTTP request into the application means that your application must make 2 extra network requests to the out-of-proc store — one to load session before the request is processed and another to store the session back after the request is done processing. This is a fairly heavy-weight request as well because the state being loaded is the entire state for the user for the entire application. In other words if you have ten pages in your application and each one puts a little bit of data into session, when you visit “page1” then you’re loading all of the session data for “page1”, “page2”, “page3”, etc. By the way, these extra network requests happen on every page even if you’re not using session on the page. There are some optimizations (EnableSessionState on the Page and SessionStateAttribute for MVC), but they don’t solve the problem entirely because the network request must happen to update the store to let it know the user is still active so that it doesn’t cleanup inactive sessions.
So back to caching briefly: this is why we prefer to use the data cache for caching and not session. If you’re using session for any user generated data, then you can’t keep it in-proc and you should keep it out-of-proc and once it’s out-of-proc then that defeats the purpose of caching. The data cache is for read-only data that can be reloaded from the original data source when the cache expires and session is meant for user driven data like shopping carts. Except session has all these problems…
So what to do for our shopping cart data? Here’s what I suggest: 1) explicitly disable session in your application’s web.config (it’s enabled by default in the machine-wide web.config), 2) keep the user data elsewhere. Elsewhere might mean in a cookie, in a hidden field, in the query string, in web storage or in the database (relational or NoSQL). Which one’s the right answer? As always, it depends. For shopping cart like data, I’d probably use the database. I’d create a custom “shopping cart” table and as users add items then you make network call to update the database. Once the user places the order then you’d clear out rows for the user’s shopping cart. As for the key in the shopping cart table you can simply use the user’s unique authentication username from User.Identity.Name.
So the complaints about my conclusion are: 1) effort, 2) network calls, 3) abandoned shopping carts and 4) what about anonymous users.
1) Some people complain that this is too much work. I’m sorry to inform you that programming is hard. Get over it. And to be honest, it’s not that hard with the modern ORMs we have of if you’re using a NoSQL database then it’s even easier. Also, this database doesn’t have to be your main production database — it could just be a database that’s for the webserver.
2) What about all these network calls? Well, if you were using session then in-proc was a non-starter and you were going to have to keep this data out-of-proc. So you were already going to incur heavy-weight network calls. With this approach only the pages that need the shopping cart data incur the network calls and the pages that don’t need the shopping cart data won’t have this burden imposed upon them. This is already better than out-of-proc session.
3) With this approach you will need to have some way to periodically cleanup abandoned shopping carts. Sure, but the application is still better off for the effort. Also, this assumes that you do want to get rid of the data, but think about amazon — they don’t ever want to get rid of shopping cart data. That data is potential sales (they remind you on every visit that you forgot to give them your money) and it’s probably useful for data mining. So maybe keeping this data is a good thing.
4) I suggest that you should use the authenticated user’s username for the key in the shopping cart table. Well, what about anonymous users? Part of the Session feature is that there is a “Session ID” associated with the user and this is sometimes quite useful. It is, but not at the expense of at of the other parts of session. So what to do? Well, there’s a little known feature in ASP.NET called the Anonymous Identification Module. This is a HttpModule whose sole purpose is to track anonymous users with an ID. If you enable this feature, then it issues a cookie with a unique ID per anonymous users. You can then access that ID in your code via Request.AnonymousID.
One last reason session is problematic: concurrent requests. Access to session (by default) is exclusive. This means that if your page makes 2 Ajax calls back to the server, ASP.NET forces one to wait while the other executes because each request is taking locks on the session. This really hampers scalability if not responsiveness. Also, with some of the new HTML5 features like Server Sent events that hold long-lived connections to the server then this can lock a user out of the server if they try to make a request at the same time.
So beware anytime someone says “use session state — it’s easy”. If something’s easy then you’re making a tradeoff. Realize what you’re trading off and just take a minute to think about a better way to store the data. Your application will be better off.