For many CRM 2011 applications, caching of commonly retrieved data is important for maintaining a high performance system. Typically, you want to cache any data that is retrieved frequently but does not change often. In a CRM system this is usually either configuration data or CRM entity metadata.
In CRM 4.0, the way you typically extended a CRM application was by creating another ASP.NET web-app that connected to CRM through the CRM web-services, and it was fairly simple to cache common data on the server using the asp.net HttpRuntime cache. For CRM 2011, the landscape has changed a bit, mostly due to the introduction of web-resources and the more frequent use of CRM Online as a hosting option. Now, the recommended best-practice is to not extend CRM using a separate ASP.NET application (if possible), but to create all of your custom UI elements using Silverlight or JavaScript web-resources, and handle all of your server-side business logic in CRM plug-ins. Here's a couple of things to keep in mind when using this new model:
You can get free client-side caching if your data is stored as a web-resource.
XML web-resources are an ideal way to store configuration info on the CRM server. When the xml file is retrieved by your client-side Silverlight or JavaScript UI components, the browser will automatically cache the retrieved file for you. This works great for most configuration info, but not for any sensitive data. All web-resources can be viewed by any user with access to CRM, and there’s no way to lock down specific resources by role. So anything sensitive such as connection strings or passwords will still need to be stored in a custom entity (which can be locked down by role). Also, CRM 2011 is designed to effectively clear any browser-cached web-resources whenever they are modified and re-published. Which leads to..
Always reference web-resources using relative URLs or the $webresource syntax.
If you reference a CRM web-resource using its full URL (for example: http://CRMServer/Org/WebResources/new_/MyWebResource.htm) you're browser won't actually cache the file. When CRM serves the file the cache-control header on the response will be set to expire immediately, and so All requests using the full URL will always retrieve the file from the server. To get browser-caching you need to reference the file using a special relative URL, which looks like this:
http://CrmServer/Org/%7B634380644480000000%7D/WebResources/new_/UI/ClientBin/MyWebResource.htm
Notice the magic number inserted into the URL? When CRM serves files from URLs in this format the cache-control header on the response will be set to expire in one year, so the browser will cache the file properly. CRM will also automatically change the unique number whenever web-resources are modified and published, causing the browser to retrieve the new version of the file.
If you add a web-resource to a CRM form using the “Add Web Resource” button, then CRM will automatically use the special URL for you. But If you are referencing a web-resource in a ribbon or in the sitemap XML then you need to make sure you always use the $webresource:MyWebResourceName syntax. This ensures that caching is enabled on the web-resource file. And if you are referencing web-resources from other web-resources (e.g., retrieving a config xml file from a Silverlight app), you should always use relative paths so that the URL contains the unique number.
Server-side caching is not available for sandboxed plug-ins.
CRM Online only lets you run plug-ins in the “sandboxed” isolation mode. This pretty much restricts your ability to use any kind of server-side caching. Using the ASP.Net http-runtime cache is not allowed, and you cannot even cache data in static variables because the sandboxing system effectively hosts the plug-in in a new process each time the plug-in is fired. You can, however, achieve a very limited sort of caching using plug-in shared variables. This data can only be shared across different plug-ins in the same execution pipeline, but it can be useful in some situations. For example: if you had multiple plug-ins on the same message that each require retrieving the same metadata, you can temporarily cache the metadata in the shared variables collection.
Clearing server-side caches on load-balanced systems.
For an on-premise CRM deployment you don’t need to run plug-ins in sandbox mode, so you can take advantage of server-side caching. However, for systems that are load-balanced and have multiple platform and application web-servers, it can be tricky to make sure that the cached data on each system is properly synchronized. To outline the problem, suppose you have a CRM system that stores data in a custom entity, and several CRM plug-ins that use this data. To reduce the number of database retrieves, the plug-ins cache the data using the ASP.NET cache. Also, you have three load-balanced application servers. Each server has its own in-memory cache, so when the configuration information changes, you need to clear the cache on all three servers.
One way to handle this is to piggy-back off of the internal messaging system that CRM uses whenever the configuration string for a plug-in is updated. Whenever you register a CRM plug-in you are given the option to define a config string that is then passed to the constructor of the plugin when it is instantiated. If the configuration string is updated (from any server), the new string will be passed to the constructor of the plug-in on all servers. So if we want to clear the cache on all servers at once, we can just change the config string to a new unique value:
In CRM 4.0, the way you typically extended a CRM application was by creating another ASP.NET web-app that connected to CRM through the CRM web-services, and it was fairly simple to cache common data on the server using the asp.net HttpRuntime cache. For CRM 2011, the landscape has changed a bit, mostly due to the introduction of web-resources and the more frequent use of CRM Online as a hosting option. Now, the recommended best-practice is to not extend CRM using a separate ASP.NET application (if possible), but to create all of your custom UI elements using Silverlight or JavaScript web-resources, and handle all of your server-side business logic in CRM plug-ins. Here's a couple of things to keep in mind when using this new model:
You can get free client-side caching if your data is stored as a web-resource.
XML web-resources are an ideal way to store configuration info on the CRM server. When the xml file is retrieved by your client-side Silverlight or JavaScript UI components, the browser will automatically cache the retrieved file for you. This works great for most configuration info, but not for any sensitive data. All web-resources can be viewed by any user with access to CRM, and there’s no way to lock down specific resources by role. So anything sensitive such as connection strings or passwords will still need to be stored in a custom entity (which can be locked down by role). Also, CRM 2011 is designed to effectively clear any browser-cached web-resources whenever they are modified and re-published. Which leads to..
Always reference web-resources using relative URLs or the $webresource syntax.
If you reference a CRM web-resource using its full URL (for example: http://CRMServer/Org/WebResources/new_/MyWebResource.htm) you're browser won't actually cache the file. When CRM serves the file the cache-control header on the response will be set to expire immediately, and so All requests using the full URL will always retrieve the file from the server. To get browser-caching you need to reference the file using a special relative URL, which looks like this:
http://CrmServer/Org/%7B634380644480000000%7D/WebResources/new_/UI/ClientBin/MyWebResource.htm
Notice the magic number inserted into the URL? When CRM serves files from URLs in this format the cache-control header on the response will be set to expire in one year, so the browser will cache the file properly. CRM will also automatically change the unique number whenever web-resources are modified and published, causing the browser to retrieve the new version of the file.
If you add a web-resource to a CRM form using the “Add Web Resource” button, then CRM will automatically use the special URL for you. But If you are referencing a web-resource in a ribbon or in the sitemap XML then you need to make sure you always use the $webresource:MyWebResourceName syntax. This ensures that caching is enabled on the web-resource file. And if you are referencing web-resources from other web-resources (e.g., retrieving a config xml file from a Silverlight app), you should always use relative paths so that the URL contains the unique number.
Server-side caching is not available for sandboxed plug-ins.
CRM Online only lets you run plug-ins in the “sandboxed” isolation mode. This pretty much restricts your ability to use any kind of server-side caching. Using the ASP.Net http-runtime cache is not allowed, and you cannot even cache data in static variables because the sandboxing system effectively hosts the plug-in in a new process each time the plug-in is fired. You can, however, achieve a very limited sort of caching using plug-in shared variables. This data can only be shared across different plug-ins in the same execution pipeline, but it can be useful in some situations. For example: if you had multiple plug-ins on the same message that each require retrieving the same metadata, you can temporarily cache the metadata in the shared variables collection.
Clearing server-side caches on load-balanced systems.
For an on-premise CRM deployment you don’t need to run plug-ins in sandbox mode, so you can take advantage of server-side caching. However, for systems that are load-balanced and have multiple platform and application web-servers, it can be tricky to make sure that the cached data on each system is properly synchronized. To outline the problem, suppose you have a CRM system that stores data in a custom entity, and several CRM plug-ins that use this data. To reduce the number of database retrieves, the plug-ins cache the data using the ASP.NET cache. Also, you have three load-balanced application servers. Each server has its own in-memory cache, so when the configuration information changes, you need to clear the cache on all three servers.
One way to handle this is to piggy-back off of the internal messaging system that CRM uses whenever the configuration string for a plug-in is updated. Whenever you register a CRM plug-in you are given the option to define a config string that is then passed to the constructor of the plugin when it is instantiated. If the configuration string is updated (from any server), the new string will be passed to the constructor of the plug-in on all servers. So if we want to clear the cache on all servers at once, we can just change the config string to a new unique value:
QueryExpression findPluginStep = new QueryExpression(SdkMessageProcessingStep.EntityLogicalName);Then, in the plugin’s constructor, if the configuration string changes, you know it’s time to clear your cache:
findPluginStep.Criteria.AddCondition("name", ConditionOperator.Equal, "MyPluginStepName");
foreach (SdkMessageProcessingStep step in service.RetrieveMultiple(findPluginStep).Entities)
{
//give the plugin step a new unique config string (the current datetime)
step.Configuration = DateTime.Now.ToString();
service.Update(step);
}
private static string _oldConfig = null;//constructorpublic MyPlugin(string config){if (_oldConfig != config){HttpRuntime.Cache.Remove(CACHE_KEY);_oldConfig = config;}}
This is an easy way to synchronize cached data across multiple servers, as CRM does the messaging for you without any extra overhead.