Skip to content

v2.0.0 Breaking Change Details

Jon Cole edited this page Sep 29, 2015 · 4 revisions

Background

In previous versions of the "Microsoft.Web.RedisSessionStateProvider" NuGet package, session data was stored in Redis using keys using the following pattern.

<Application Name>_<Session ID>_Data
<Application Name>_<Session ID>_Write_Lock
<Application Name>_<Session ID>_Internal

With Redis clustering, there is no guarantee that these three keys would all land on the same shard (instance) of Redis. However, it is important that the data is on the same shard. When you add brackets ('{' and '}') to the key, only the part of the key that is inside the brackets is used by Redis when hashing the key to particular shard. So now all three keys are in same shard. The new key format looks like this:

{<Application Name>_<Session ID>}_Data
{<Application Name>_<Session ID>}_Write_Lock
{<Application Name>_<Session ID>}_Internal

Migrating Existing Session Data

Unfortunately, changing the key format means that existing session data won't be found. In your application, you can add code like the sample below to your Global.asax.cs to migrate session data from old format to new format when any request comes (session will be converted only when a request to access that session comes to the app). This migration only needs to happen once. After all keys have been updated to the new format, you can remove this code.

// KEYS[] = data-id, internal-id, lock-id, new-data-id, new-internal-id, new-lock-id
readonly string renameSessionScript = (@" 
        if redis.call('EXISTS', KEYS[1]) ~= 0 then
            local expiretime = redis.call('TTL',KEYS[1]) 
            redis.call('RENAME',KEYS[1],KEYS[4]) 
            redis.call('EXPIRE',KEYS[1], expiretime) 
        end
        if redis.call('EXISTS', KEYS[2]) ~= 0 then
            local expiretime = redis.call('TTL',KEYS[2]) 
            redis.call('RENAME',KEYS[2],KEYS[5]) 
            redis.call('EXPIRE',KEYS[2], expiretime) 
        end
        if redis.call('EXISTS', KEYS[3]) ~= 0 then
            local expiretime = redis.call('TTL',KEYS[3]) 
            redis.call('RENAME',KEYS[3],KEYS[6]) 
            redis.call('EXPIRE',KEYS[3], expiretime) 
        end
        return 1");

private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
    //Make sure to put cache name and password in bellow line.
    return ConnectionMultiplexer.Connect("mycache.redis.cache.windows.net,abortConnect=false,ssl=true,password=...");
});

public static ConnectionMultiplexer Connection
{
    get
    {
        return lazyConnection.Value;
    }
}

void Application_AuthorizeRequest(object sender, EventArgs e)
{
    string sessionId = this.Request.Params.Get("ASP.NET_SessionId");
    if(!string.IsNullOrEmpty(sessionId))
    {
        IDatabase connection = Connection.GetDatabase();
        string applicationName = "/"; // application name that you might have specified in web.config if not than default is '/'.
        string appIdAndSessionId = applicationName + "_" + sessionId;

        RedisKey[] keys = new RedisKey[6];
        keys[0] = appIdAndSessionId + "_Data";
        keys[1] = appIdAndSessionId + "_Write_Lock";
        keys[2] = appIdAndSessionId + "_Internal";
        keys[3] = "{" + appIdAndSessionId + "}_Data";
        keys[4] = "{" + appIdAndSessionId + "}_Write_Lock";
        keys[5] = "{" + appIdAndSessionId + "}_Internal";
        connection.ScriptEvaluate(renameSessionScript, keys, new RedisValue[0]);
    }
}

Server Farms

In a server farm scenario, you will need to update all farm instances at the same time. If only one server is updated with new session state provider, then you can run into a situation where one request comes to server with new session state which converts session data from old format to new format. Now, some other request can come to a server instance running the old session state provider code which will now fail to find the session as it was converted earlier.

You can’t perform that action at this time.