简体   繁体   中英

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");
    }
}

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)

Class level variables should be limited to configuration information. Using a class level variable as you are doing is not supported. In CRM Online, because of multiple web front ends, a specific request may be executed on a different server by a different instance of the plugin class than another request. Overall, assume CRM is stateless and that unless persisted and retrieved nothing should be assumed to be continuous between plugin executions.

Per the SDK:

The plug-in's Execute method should be written to be stateless because the constructor is not called for every invocation of the plug-in. Also, multiple system threads could execute the plug-in at the same time. All per invocation state information is stored in the context, so you should not use global variables or attempt to store any data in member variables for use during the next plug-in invocation unless that data was obtained from the configuration parameter provided to the constructor.

Reference: https://msdn.microsoft.com/en-us/library/gg328263.aspx

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM