Func<string> DoSomething: Type [...] Controller+<>c__DisplayClass0_0 is not marked as serializable

75 Views Asked by At

I'm trying to implement FileCache (https://github.com/acarteas/FileCache) which is based on ObjectCache. I'm trying to check a cache object for its existence, or add it if required and return. When the object does not exist however, the delegate is not executed and an error is thrown: Type 'myNamespace.Controllers.ListController+<>c__DisplayClass0_0' in [...] is not marked as serializable.

What I've tried (simplified):

private string generateString(int? Id)
{ 
    return "String";
}

public ActionResult GetSomething(int? Id)
{
    var cacheFilePath = $"{AppDomain.CurrentDomain.BaseDirectory}{"\\cache"}";
    var cache = new FileCache(cacheFilePath, false, TimeSpan.FromSeconds(30));
    if (purgeCache)
        cache.Remove($"CacheKey{Id}");

    Func<string> createString = () => generateString(Id);

    var myString = (string) cache.AddOrGetExisting($"CacheKey{Id}", createString, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(30) });
    return new JsonStringResult(myString);

}

Ok, now I've tried to specify the delegate createString with Serializable, but that doesn't work. Could someone point me into the right direction?

Essentially I want: - run a statement that returns a previous output of generateString(123); if it doesn't exist or is expired, it should re-generate it.

Thanks for any tips!

2

There are 2 best solutions below

2
On BEST ANSWER

Due to the nature of FileCache, I think the only reasonable way to do that is to fallback to the usual way - check if item exists in cache and if not - add it:

private static readonly _cacheLock = new object();
private static readonly FileCache _cache = new FileCache($"{AppDomain.CurrentDomain.BaseDirectory}{"\\cache"}", false, TimeSpan.FromSeconds(30));
/// ...
lock (_cacheLock) {
    var myString = _cache.Get($"CacheKey{Id}");
    if (myString == null) {
        myString = generateString(Id);
        _cache.Set($"CacheKey{Id}", myString, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(30) });
    }
}

Lock is necessary because FileCache both writes and reads from the same file, and this is never safe to do from multiple threads.

2
On

The signature for AddOrGetExisting says the second parameter is object value and not a callback delegate:

https://github.com/acarteas/FileCache/blob/master/src/FileCache/FileCache.cs

public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)

I think you just want this (I've corrected other potential issues in your code too):

public ActionResult GetSomething(int? id)
{
    String cacheFilePath = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "Cache" );
    FileCache cache = new FileCache( cacheFilePath, false, TimeSpan.FromSeconds(30) );

    String cacheKey = String.Format( CultureInfo.InvariantCulture, "CacheKey{0}", id );
    if( purgeCache ) cache.Remove( cacheKey );

    String valueString = this.GenerateString( id );

    String myString = (String)cache.AddOrGetExisting( cacheKey, valueString, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(30) });
    return new JsonStringResult( myString );

}
  1. C# Interpolated strings $"like {this}" are not suitable for use outside of UI code because they automatically use CultureInfo.CurrentCulture which results in inconsistent output depending on the current thread's culture which in ASP.NET is automatically set to the visitor's browser's Accept-Language header value. It's best to use an explict String.Format( CultureInfo.InvariantCulture, format, args ) instead.
  2. Your code had redundant steps for generating the cache key, I moved it to a single variable cacheKey instead.
  3. C# naming conventions use camelCase for parameters and locals, and PascalCase for methods.