Search code examples
c#dynamics-crm-2011dynamics-crmdynamics-crm-2013dynamics-crm-online

Dynamics CRM Online Object caching not caching correctly


I have a requirement where we need a plugin to retrieve a session id from an external system and cache it for a certain time. I use a field on the entity to test if the session is actually being cached. When I refresh the CRM form a couple of times, from the output, it appears there are four versions (at any time consistently) of the same key. I have tried clearing the cache and testing again, but still the same results.

Any help appreciated, thanks in advance.

Output on each refresh of the page:

20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0

20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0

20170511_125410:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0

20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0

20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0

20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0

20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0

20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0

To accomplish this, I have implemented the following code:

public class SessionPlugin : IPlugin
{
    public static readonly ObjectCache Cache = MemoryCache.Default;
    private static readonly string _sessionField = "new_sessionid";
    #endregion

    public void Execute(IServiceProvider serviceProvider)
    {
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

        try
        {
            if (context.MessageName.ToLower() != "retrieve" && context.Stage != 40)
                return;

            var userId = context.InitiatingUserId.ToString();

            // Use the userid as key for the cache
            var sessionId = CacheSessionId(userId, GetSessionId(userId));
            sessionId = $"{sessionId}:{Cache.Select(kvp => kvp.Key == userId).ToList().Count}:{userId}";

            // Assign session id to entity
            var entity = (Entity)context.OutputParameters["BusinessEntity"];

            if (entity.Contains(_sessionField))
                entity[_sessionField] = sessionId;
            else
                entity.Attributes.Add(new KeyValuePair<string, object>(_sessionField, sessionId));
        }
        catch (Exception e)
        {
            throw new InvalidPluginExecutionException(e.Message);
        }
    }

    private string CacheSessionId(string key, string sessionId)
    {
        // If value is in cache, return it
        if (Cache.Contains(key))
            return Cache.Get(key).ToString();

        var cacheItemPolicy = new CacheItemPolicy()
        {
            AbsoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration,
            Priority = CacheItemPriority.Default
        };

        Cache.Add(key, sessionId, cacheItemPolicy);

        return sessionId;
    }

    private string GetSessionId(string user)
    {
        // this will be replaced with the actual call to the external service for the session id
        return DateTime.Now.ToString("yyyyMMdd_hhmmss");
    }
}

Solution

  • This has been greatly explained by Daryl here: https://stackoverflow.com/a/35643860/7708157

    Basically you are not having one MemoryCache instance per whole CRM system, your code simply proves that there are multiple app domains for every plugin, so even static variables stored in such plugin can have multiple values, which you cannot rely on. There is no documentation on MSDN that would explain how the sanboxing works (especially app domains in this case), but certainly using static variables is not a good idea.Of course if you are dealing with online, you cannot be sure if there is only single front-end server or many of them (which will also result in such behaviour)