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 });
string cacheId = NormalizeUrl(HttpContext.Current.Request.Path);
if (contextCacheManager != null)
{
object cachedObjectFactory = contextCacheManager.GetType().InvokeMember("ObjectFactory",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty,
null, contextCacheManager, new object[] {});
cachedObjectFactory.GetType().InvokeMember("FlushItem", BindingFlags.Instance |
BindingFlags.Public | BindingFlags.InvokeMethod,
null, cachedObjectFactory, new object[] { cacheId });
}
else
{
Microsoft.Office.Server.Diagnostics.PortalLog.LogString("Unexpected error: DualLayout " +
"FlushCurrentPublishingPageFromCache: No CacheManager for page {0}", 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;
}