Search code examples
c#-4.0deserializationbooksleeve

What is the proper way to deserialize a RedisMessage through BookSleeve?


I have created the following singleton class to handle a Redis connection, and expose BookSleeve functionality:

public class RedisConnection
{
    private static RedisConnection _instance = null;
    private BookSleeve.RedisSubscriberConnection _channel;
    private readonly int _db;
    private readonly string[] _keys;                        // represent channel name


    public BookSleeve.RedisConnection _connection;


    /// <summary>
    /// Initialize all class parameters
    /// </summary>
    private RedisConnection(string serverList, int db, IEnumerable<string> keys) 
    {
        _connection = ConnectionUtils.Connect(serverList);
        _db = db;
        _keys = keys.ToArray();

        _connection.Closed += OnConnectionClosed;
        _connection.Error += OnConnectionError;

        // Create a subscription channel in redis
        _channel = _connection.GetOpenSubscriberChannel();

        // Subscribe to the registered connections
        _channel.Subscribe(_keys, OnMessage);

        // Dirty hack but it seems like subscribe returns before the actual
        // subscription is properly setup in some cases
        while (_channel.SubscriptionCount == 0)
        {
            Thread.Sleep(500);
        }
    }

    /// <summary>
    /// Do something when a message is received
    /// </summary>
    /// <param name="key"></param>
    /// <param name="data"></param>
    private void OnMessage(string key, byte[] data)
    {
        // since we are just interested in pub/sub, no data persistence is active
        // however, if the persistence flag is enabled, here is where we can save the data

        // The key is the stream id (channel)
        //var message = RedisMessage.Deserialize(data);
        var message = Helpers.BytesToString(data);

        if (true) ;

        //_publishQueue.Enqueue(() => OnReceived(key, (ulong)message.Id, message.Messages));
    }

    public static RedisConnection GetInstance(string serverList, int db, IEnumerable<string> keys) 
    {
        if (_instance == null)
        {
            // could include some sort of lock for thread safety
            _instance = new RedisConnection(serverList, db, keys);
        }

        return _instance;
    }



    private static void OnConnectionClosed(object sender, EventArgs e)
    {
        // Should we auto reconnect?
        if (true)
        {
            ;
        }
    }

    private static void OnConnectionError(object sender, BookSleeve.ErrorEventArgs e)
    {
        // How do we bubble errors?
        if (true)
        {
            ;
        }
    }
}

In OnMessage(), var message = RedisMessage.Deserialize(data); is commented out due to the following error:

RedisMessage is inaccessible due to its protection level.

RedisMessage is an abstract class in BookSleeve, and I'm a little stuck on why I cannot use this.

I ran into this issue because as I send messages to a channel (pub/sub) I may want to do something with them in OnMessage() - for example, if a persistence flag is set, I may choose to begin recording data. The issue is that the data is serialized at this point, and I wish to deserialize it (to string) and and persist it in Redis.

Here is my test method:

    [TestMethod]
    public void TestRedisConnection()
    {
        // setup parameters
        string serverList = "dbcache1.local:6379";
        int db = 0;

        List<string> eventKeys = new List<string>();
        eventKeys.Add("Testing.FaucetChannel");

        BookSleeve.RedisConnection redisConnection = Faucet.Services.RedisConnection.GetInstance(serverList, db, eventKeys)._connection;

        // broadcast to a channel
        redisConnection.Publish("Testing.FaucetChannel", "a published value!!!");

    }

Since I wasn't able to make use of the Deserialize() method, I created a static helper class:

public static class Helpers
{
    /// <summary>
    /// Serializes a string to bytes
    /// </summary>
    /// <param name="val"></param>
    /// <returns></returns>
    public static byte[] StringToBytes(string str)
    {
        try
        {
            byte[] bytes = new byte[str.Length * sizeof(char)];
            System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
            return bytes;
        }
        catch (Exception ex) 
        { 
            /* handle exception omitted */
            return null;
        }
    }


    /// <summary>
    /// Deserializes bytes to string
    /// </summary>
    /// <param name="bytes"></param>
    /// <returns></returns>
    public static string BytesToString(byte[] bytes)
    {
        string set;
        try
        {
            char[] chars = new char[bytes.Length / sizeof(char)];
            System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
            return new string(chars);
        }
        catch (Exception ex)
        {
            // removed error handling logic!
            return null;
        }
    }


}

Unfortunately, this is not properly deserializing the string back to its original form, and what I'm getting is something like this: ⁡異汢獩敨⁤慶畬㩥ㄠ, rather than the actual original text.

Suggestions?


Solution

  • RedisMessage represents a pending request that is about to be sent to the server; there are a few concrete implementations of this, typically relating to the nature and quantity of the parameters to be sent. It makes no sense to "deserialize" (or even "serialize") a RedisMessage - that is not their purpose. The only thing it is sensible to do is to Write(...) them to a Stream.

    If you want information about a RedisMessage, then .ToString() has an overview, but this is not round-trippable and is frankly intended for debugging.

    RedisMessage is an internal class; an implementation detail. Unless you're working on a pull request to the core code, you should never need to interact with a RedisMessage.

    At a similar level, there is RedisResult which represents a response coming back from the server. If you want a quick way of getting data from that, fortunately that is much simpler:

    object val = result.Parse(true);
    

    (the true means "speculatively test to see if the data looks like a string"). But again, this is an internal implementation detail that you should not have to work with.