Getting the following exception when trying to pull an object out of the cache, it was added fine
this is what it added to the cache
Getting the following exception when trying to pull an object out of the cache, it was added fine
This points to a mismatch in the type of object being added to cache vs how it's being gotten from cache. The cache methods are strongly typed now, relying on generic methods for ensuring the same type is added/pulled from cache.
In particular:
// given an object of type nullable LastReadPostInfo...
FourRoads.VerintCommunity.ForumLastPost.Interfaces.LastReadPostInfo? lastReadPostInfo = null;
Telligent.Evolution.Extensibility.Caching.Version1.CacheService.Put("my-key", CacheScope.Process, lastReadPostInfo); ;
// Retrieve from cache with same nullable type
if (Telligent.Evolution.Extensibility.Caching.Version1.CacheService.TryGet<FourRoads.VerintCommunity.ForumLastPost.Interfaces.LastReadPostInfo?>("my-key", CacheScope.Process, out var cachedLastReadPostInfo))
{
// do something with cachedLastReadPostInfo
}Incidentally, we're reviewing our options with potentially de-obfuscating portions of logged exception stacks.
It's inside this method, which does what you are doing above, the key is quite complex, but I can't see how that would cause a cast issue
public T Get<T>(string key)
{
CacheService.TryGet<T>(key, CacheScope.All, out T result);
return result;
}


The Get is fine. But it still likely means it's being put in the cache as an Object, not a Nullable<LastReadPostInfo>.
The Get is fine. But it still likely means it's being put in the cache as an Object, not a Nullable<LastReadPostInfo>.
And as you're specifying CacheScope.All, make sure there's a corresponding IDistributedCacheSerializationProvider Plugin Type .
You are right, I assume as it's a struct it is boxing it and then it breaks
public void Insert(string key, object value, string[] tags, TimeSpan timeout)
{
CacheService.Put(key, CacheScope.All, value, new PutOptions
{
ExpiresAfter = timeout,
Tags = tags ?? Array.Empty<string>()
});
}
Tell me more about IDistributedCacheSerializationProvider and it's intent?
Correct. You'd want to use this generic version instead which doesn't box it as an object.
public void Insert<T>(string key, T value, string[] tags, TimeSpan timeout)
{
CacheService.Put(key, CacheScope.All, value, new PutOptions
{
ExpiresAfter = timeout,
Tags = tags ?? Array.Empty<string>()
});
}Tell me more about IDistributedCacheSerializationProvider and it's intent?
Previous versions of caching relied on .NET's binary formatting when serializing/deserializing objects for the distributed cache. Binary formatting was removed as of .NET 9 due to security concerns.

The typical workaround is to use an explicit serialization format instead, JSON, MessagePack, etc.
IDistributedCacheSerializationProvider is a way for a plugin to define how a custom type can be serialized to and from a byte array, and the caching service looks for one if it's asked to serialize a type it doesn't know about for the distributed cache.
It's not required if only caching in-process, since those are just live, non-serialized, objects.
is there not a default platform IDistributedCacheSerializationProvider implimentation ?
There is built in support for all simple types and cacheable API types. But any custom object that needs to be put into cache needs a corresponding IDistributedCacheSerializationProvider.
The problem or interesting thing is that
LastReadPostInfo?, you should also get LastReadPostInfo?. If you put LastReadPostInfo, you should get LastReadPostInfo.Put implementation is generic, as suggested. Changing the wrapper's Put to accept an object does result in your original exception.CacheScope.All, I’ve implemented IDistributedCacheSerializationProvider. However, the provider is intentionally limited to the specific type this plugin owns and understands: LastReadPostInfo.Are you saying that for every entity we have defined in a project we will have to register a specific serializer?
IDistributedCacheSerializationProvider, as shown in the earlier example, for a few reasons:IDistributedCacheSerializationProvider when using the distributed cache, since only the developer can know the intended serialization behavior for the plugin’s own types.IDistributedCacheSerializationProvider that matches all types, T, it will break other plugins as well as the underlying platform itself which often have their own specific serialization formats for given types.IDistributedCacheSerializationProvider only handles the types you explicitly intend to support.using Telligent.Evolution.Extensibility;
using Telligent.Evolution.Extensibility.Api.Version1;
using Telligent.Evolution.Extensibility.Caching.Version1;
using Telligent.Evolution.Extensibility.Version1;
namespace FourRoads.CacheTest;
public class CacheTestPlugin : IPlugin, IDistributedCacheSerializationProvider
{
public string Name => "Four Roads Cache Test";
public string Description => Name;
public void Initialize()
{
LastReadPostInfo? postInfoToCache = new LastReadPostInfo
{
ReplyCount = 5,
ContentId = Guid.NewGuid(),
PostDate = DateTime.UtcNow
};
string myCacheKey = "my-test-cache-key";
// put in cache
TestCacheWrapper.Insert(myCacheKey, postInfoToCache, ["tag1", "tag2"], TimeSpan.FromMinutes(10));
// get from cache
var cachedPostInfo = TestCacheWrapper.Get<LastReadPostInfo?>(myCacheKey);
// verify that the cached item matches original
bool success = cachedPostInfo?.ContentId == postInfoToCache?.ContentId &&
cachedPostInfo?.ReplyCount == postInfoToCache?.ReplyCount &&
cachedPostInfo?.PostDate == postInfoToCache?.PostDate;
Apis.Get<IEventLog>().Write($"LastReadPostInfo cached? {success}", new EventLogEntryWriteOptions
{
EventType = "Information",
Category = "FourRoadsCacheTest",
EventId = 1001
});
}
#region IDistributedCacheSerializationProvider
private readonly HashSet<Type> _serializableTypes = new()
{
{ typeof(LastReadPostInfo?) }
};
public IDistributedCacheSerializer<T> GetSerializer<T>()
{
if (_serializableTypes.Contains(typeof(T)))
return new TestDistributedCacheSerializer<T>();
return null!;
}
#endregion
}
public class TestDistributedCacheSerializer<T> : IDistributedCacheSerializer<T>
{
public byte[] Serialize(T value)
{
return System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(value);
}
public T Deserialize(byte[] value)
{
return System.Text.Json.JsonSerializer.Deserialize<T>(value)!;
}
}
public static class TestCacheWrapper
{
public static void Insert<T>(string key, T value, string[] tags, TimeSpan timeout)
{
CacheService.Put(key, CacheScope.All, value, new PutOptions
{
ExpiresAfter = timeout,
Tags = tags ?? Array.Empty<string>()
});
}
public static T Get<T>(string key)
{
CacheService.TryGet<T>(key, CacheScope.All, out T result);
return result;
}
}
public struct LastReadPostInfo
{
public int ReplyCount { get; set; }
public Guid? ContentId { get; set; }
public DateTime PostDate { get; set; }
}
Your sample is very close to what I have ended up with. You are right the Put and the Get's must match types and this is now happening because of things being strongly typed rather than just storing objects. The risk here in projects is that with structs in particular you can fall into the trap of Putting Type and Getting Type? or vis-versa.