Writing Your Session Store Provider ASP.NET Using Redis

In this article I would like to share the experience of writing my own Session Store Provider using Redis as a repository.
A lot has been written about Redis. But in short, Redis is a very fast storage in key-value format (and not only). It seems to me that it is very well suited for such tasks and it has long been interesting for me to try it in my project.
Also, for those who want to try the result in action, I will give a link to the source.
Why all this is needed
I needed a session repository that meets the following requirements:
1. Control over the process of recording / reading a session. I will
explain later why.
2. Distribution between multiple instances of the application.
To scale the application to multiple servers.
3. The speed.
Everything is clear why.
Disclaimer
I must say right away that while this code is at the prototype stage, made in my spare time just to see what comes of it. I did not decide to use it in the project or not, but I think the one described here will be interesting to someone. There is also the mercenary intent of this publication - maybe someone will point out the shortcomings of this approach before I finally decide to use it in production :)
I use Redis for almost a couple of hours, so there is probably something to improve (I will be glad hear it in the comments).
Redis installation process omitted. I can say that I uploaded Ubuntu Server to VirtualBox, and then it was a matter of several commands and editing several files (for a static IP and for allowing Redis to accept connections not only from localhost).
Also, I must mention that there are already existing solutions, for example Windows Server AppFabric Cache with its session provider, but because of point 1, you still need to write your provider (using only its cache functionality), and in terms of speed it seems to me Redis will be faster.
Why do you need your own provider
The session is essentially a collection of key-value, this dictionary is obtained at the beginning of the request, and recorded (in case of changes) at the end of the request.
If you have several requests that use the session , and not only read-only, but also change some values in it, then these requests cannot be executed in parallel . This is because in standard providers, only 1 request can use an exclusive session (which is necessary for recording).
We have several blocks on each page that are loaded in parallel (AJAX), and all require access to the session (each block changes its variables). With any standard provider, they are executed one by one. And we need in parallel.
Classes for implementing the Session Store Provider
There are 2 main and 1 additional classes that you need to implement for your session provider.
- RedisNoLockSessionStateStoreProvider inherited from SessionStateStoreProviderBase. Responsible for “getting” the session (i.e. the whole collection) from the repository. Here it should be noted that at first they were always confused by the names of his methods: GetItem, RemoveItem, ... By the names you can decide that the class is responsible for individual objects in the session, but this is not so.
In an implementation in which session elements can be changed in several queries in parallel, this class takes almost nothing upon itself.Copy Source | Copy HTML- public class RedisNoLockSessionStateStoreProvider : SessionStateStoreProviderBase
- {
- //...
- public override SessionStateStoreData CreateNewStoreData(System.Web.HttpContext context, int timeout)
- {
- string sessionId = context.Request.Cookies[RedisNoLockSessionIDManager.CookieName].Value;
- return new SessionStateStoreData(new RedisNoLockSessionStateItemsCollection(sessionId, _defaultTimeout, _redisServer, _redisDb), SessionStateUtility.GetSessionStaticObjects(context), timeout);
- }
- public override SessionStateStoreData GetItem(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
- {
- lockAge = TimeSpan.Zero;
- lockId = null;
- locked = false;
- actions = SessionStateActions.None;
- //Вот тут мы и возвращаем коллекцию RedisNoLockSessionStateItemsCollection
- return new SessionStateStoreData(new RedisNoLockSessionStateItemsCollection(id, _defaultTimeout, _redisServer, _redisDb), SessionStateUtility.GetSessionStaticObjects(context), _defaultTimeout);
- }
- public override SessionStateStoreData GetItemExclusive(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
- {
- return this.GetItem(context, id, out locked, out lockAge, out lockId, out actions);
- }
- //...
- }
- The RedisNoLockSessionStateItemsCollection inherited from ISessionStateItemCollection is just the class that implements everything necessary for accessing the elements of the session. In the current implementation, he takes on almost all the work.
I think from the code below it is clear how everything is arranged there. The idea is that every item is obtained and stored separately.Copy Source | Copy HTML- public class RedisNoLockSessionStateItemsCollection : RedisSessionConfig, ISessionStateItemCollection
- {
- //...
- public object this[string name]
- {
- get
- {
- string key = GetKey(name);
- using (var redis = SingleRedisPool.GetClient(_redisServer))
- {
- return redis.Get