SharePoint WCM: flushing publishing pages from the cache
SharePoint WCM does a lot of caching. One of the things that is cached are the publishing pages. These pages are cached in the object cache. Sometimes there is a situation where you want to flush a publishing page from the cache. In my case I had to flush a publishing page from the cache in a http module. The cache id for this page is the server relative url without any characters after the url. For example: /Pages/MyFirstLittleWCMPage.aspx. Therefore the path must be "normalized" so additional "stuff" is removed. The NormalizeUrl() function does this job.
What I want to do to flush the page from the cache was:
CacheManager contextCacheManager = CacheManager.GetManager(SPContext.Current.Site);
contextCacheManager.ObjectFactory.FlushItem(NormalizeUrl(HttpContext.Current.Request.Path);
Sadly enough many interesting and powerful API classes are internal, and you need some reflection to be able to call them. Below the code I needed to write to accomplish the above. I can tell you it was a hell of a job to get to this code. That is why I share it, to give you some insight in the required magic called reflection.
Interesting components:
- I know that the assembly containing the required class is already loaded. I can do GetAssembly(typeof(PublishingPage)) to get the assembly. Will work on any class in the assembly.
- To invoke a member of a class you need the type of the class. Assembly.GetType("full.name.of.type") returns the type, also on internal classes.
- Given the type you can invoke members, where members can be static functions, properties or methods. You specify what to search for the member using BindingFlags. For example for a static public method specify BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod.
- Arguments to methods must be passed in an object array.
I hope the code below will give some insight in how to make the impossible possible.
/// <summary> /// Flush the current publishing page from the object cache /// </summary> /// <remarks> /// Reflection is used to get access to internal classes of the SharePoint framework /// </remarks> private void FlushCurrentPublishingPageFromCache() { // We need to get access to the Microsoft.SharePoint.Publishing.dll assembly, PublisingPage is in there for sure Assembly microsoftSharePointPublishingAssembly = Assembly.GetAssembly(typeof(PublishingPage)); Type cacheManagerType = microsoftSharePointPublishingAssembly.GetType("Microsoft.SharePoint.Publishing.CacheManager", true); object contextCacheManager = cacheManagerType.InvokeMember("GetManager", BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod, null, null, new object[] { SPContext.Current.Site });<span style="color: blue">string </span>cacheId = NormalizeUrl(<span style="color: #2b91af">HttpContext</span>.Current.Request.Path); <span style="color: blue">if </span>(contextCacheManager != <span style="color: blue">null</span>) { <span style="color: blue">object </span>cachedObjectFactory = contextCacheManager.GetType().InvokeMember(<span style="color: #a31515">"ObjectFactory"</span>, <span style="color: #2b91af">BindingFlags</span>.Instance | <span style="color: #2b91af">BindingFlags</span>.Public | <span style="color: #2b91af">BindingFlags</span>.GetProperty, <span style="color: blue">null</span>, contextCacheManager, <span style="color: blue">new object</span>[] {}); cachedObjectFactory.GetType().InvokeMember(<span style="color: #a31515">"FlushItem"</span>, <span style="color: #2b91af">BindingFlags</span>.Instance | <span style="color: #2b91af">BindingFlags</span>.Public | <span style="color: #2b91af">BindingFlags</span>.InvokeMethod, <span style="color: blue">null</span>, cachedObjectFactory, <span style="color: blue">new object</span>[] { cacheId }); } <span style="color: blue">else </span>{ Microsoft.Office.Server.Diagnostics.<span style="color: #2b91af">PortalLog</span>.LogString(<span style="color: #a31515">"Unexpected error: DualLayout " +<br /> "FlushCurrentPublishingPageFromCache: No CacheManager for page {0}"</span>, cacheId); }
}
/// <summary> /// Normalize url for cachId usage /// </summary> /// <remarks> /// This code is copied from: /// private static string NormalizeUrl(string url); /// Declaring Type: Microsoft.SharePoint.Publishing.CachedObjectFactory /// Assembly: Microsoft.SharePoint.Publishing, Version=12.0.0.0 /// </remarks> /// <param name="url">Url to normalize</param> /// <returns>The normalized url</returns> private static string NormalizeUrl(string url) { url = SPHttpUtility.UrlPathDecode(url, false); if (!string.IsNullOrEmpty(url)) { int length = url.IndexOf('?'); if (length >= 0) { url = url.Substring(0, length); } } else { return ""; } int index = url.IndexOf('#'); if (index >= 0) { url = url.Substring(0, index); } return url; }