10

I'm trying to create a Redis provider for Strathweb.CacheOutput.WebApi2, but trying to convert from a byte[] -> RedisValue -> byte[] is returning null.

I can manually set the object type as byte[] instead of var / RedisValue and it will correctly return the value as a byte[], but after it has been set as a RedisValue its failing to convert it to a byte[].

His Interface has the Get always return an object so I can't force the type or use a separate call without having to modify the interface.

If I try to do an result as byte[] I get Cannot convert type 'StackExchange.Redis.RedisValue' to 'byte[]' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion

If I try to do a (byte[])result I get Cannot cast 'result' (which has an actual type of 'StackExchange.Redis.RedisValue') to 'byte[]'

Is there something I'm missing or am I going to have to hack it in somehow by checking what type of data its looking for based on the key?

Here is the interface:

namespace WebApi.OutputCache.Core.Cache
{
    public interface IApiOutputCache
    {
        void RemoveStartsWith(string key);
        T Get<T>(string key) where T : class;
        object Get(string key);
        void Remove(string key);
        bool Contains(string key);
        void Add(string key, object o, DateTimeOffset expiration, string dependsOnKey = null);
        IEnumerable<string> AllKeys { get; }
    }
}

And here is how its called:

        var val = _webApiCache.Get(cachekey) as byte[];
        if (val == null) return;

Edit: Adding examples of the API I implemented using both ServiceStack.Redis v3 (working atm as it just uses object and StackExchange.Redis which is not working)

https://github.com/mackayj/WebApi.OutputCache.Redis.ServiceStack

https://github.com/mackayj/WebApi.OutputCache.Redis.StackExchange

3 Answers 3

5

That's an interesting problem. There are a few ways I can think of approaching it:

  • cast it to a byte[] before storing it
  • cheat with dynamic
  • wrap it in some other identifiable wrapper prior to storing it

Essentially, custom conversion operators don't work when unboxing, unless you use dynamic

I could also perhaps implement IConvertible or some other well-known interface.

Sign up to request clarification or add additional context in comments.

11 Comments

The more I think about it, the more sure I am that RedisValue should : IConvertible. That will probably be in the next drop.
Thanks for the quick reply Marc! I replaced that code with a direct call that returns byte[] instead of object and notice its actually failing in multiple other places as well (as string and as MediaTypeHeaderValue calls). I tried just setting it as a dynamic result = _database.GetString(key) but that gave the same result, what did you mean by cheating it with dynamic? Is there a way to just have it return an object instead of converting it to the RedisValue?
I ended up just switching out StackExchange.Redis for ServiceStack.Redis and I was able to do it without issue. I really like your version better as its much smaller. If there is a way to just use object not RedisValue as the return type please let me know so I can swap it back :)
@John again, the problem is simply: what should the "object" be? the byte[]? the string? the int? if you always want byte[], then simply cast to byte[] before object, i.e. RedisValue val = ...; object o = (byte[])val; // <=== use that
well unfortunately that code is being re-used in multiple locations, one to get the etag (string), one to the the data (byte[]) and one to get the header (MediaTypeHeaderValue), so I can't just tell it to always convert to a byte[] before returning. I'm wishing those were three separate calls from the API as it would have been very easy to do then.
|
5

The following code using StackExchange.Redis can set/get value of generic type and convert RedisValue to byte[] in process, it should work fine for any serializable type.

    public static void SetItem<T>(string key, T value)
    {

        IDatabase redDb = GetDB();
        redDb.StringSet(key, ToByteArray<T>(value));
    }

    public static T GetItem<T>(string key)
    {
        IDatabase redDb = GetDB();
        RedisValue redisResult = redDb.StringGet(key);
        T objResult = FromByteArray<T>(redisResult);
        return objResult;
    }

   public static byte[] ToByteArray<T>(T obj)
   {
        if (obj == null)
            return null;
        BinaryFormatter bf = new BinaryFormatter();
        using (MemoryStream ms = new MemoryStream())
        {
            bf.Serialize(ms, obj);
            return ms.ToArray();
        }
    }

    public static T FromByteArray<T>(byte[] data)
    {
        if (data == null)
            return default(T);
        BinaryFormatter bf = new BinaryFormatter();
        using (MemoryStream ms = new MemoryStream(data))
        {
            object obj = bf.Deserialize(ms);
            return (T)obj;
        }
    }

Comments

4

The conversion between byte[] and RedisValue uses a conversion operator. The value is only known to the compiler as an object, though, so it doesn't know that it needs to call the conversion operator. I.e. if you write the following code:

object result = someRedisValue;
byte[] bytes = (byte[])result;

The compiler writes something like the following for the last line, which fails:

cast result to byte[] // runtime error: it's not a byte[]!
store that in 'bytes'

You can solve this by letting the compiler know the real type of the object before you try to convert it.

byte[] bytes = (RedisValue)result;

This causes the compiler to write code like this:

cast result to RedisValue
call RedisValue's implicit RedisValue to byte[] conversion on that
store that in 'bytes'

1 Comment

The problem is the Interface is returning an object and the calling code is doing an as byte[]. I'm trying to not modify either the interface or the main code for it but I think I'm just going to add a new method that returns the byte[] for now. I edited my post to show the correct calling code with as byte[]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.