Using Post Caching Substitution in SharePoint 2007 Web Parts

There are good number of articles that explains the different caching option Microsoft Office SharePoint Server 2007 provides and ways to leverage them to achieve better site performance. However, there are scenarios where you might want to implement output caching on your site/page, but have some controls (Web Parts, User Control) excluded from caching so that they dynamically have their content updated on every request.

These are the kind of scenarios where you'd be using Post Caching Substitution. I first setup, output caching on a SharePoint 2007 site that was built using collaboration site definition. I created my own caching profile and below are the settings I chose when I created it:

image

I then enabled output cache and set this up for both anonymous and authenticated cache profiles as shown below:

image

To verify if my caching works, I wrote a small web part that shows the current date/time whenever it runs. Code below:

    1: using System;
    2: using System.Runtime.InteropServices;
    3: using System.Web.UI;
    4: using System.Web.UI.WebControls;
    5: using System.Web.UI.WebControls.WebParts;
    6: using System.Xml.Serialization;
    7:  
    8: using Microsoft.SharePoint;
    9: using Microsoft.SharePoint.WebControls;
   10: using Microsoft.SharePoint.WebPartPages;
   11:  
   12: namespace HellWorld
   13: {
   14:     [Guid("<random guid>")]
   15:     public class HellWorld : System.Web.UI.WebControls.WebParts.WebPart
   16:     {
   17:         public HellWorld()
   18:         {
   19:         }
   20:  
   21:         protected override void Render(HtmlTextWriter writer)
   22:         {
   23:             writer.WriteLine(DateTime.Now.ToString());            
   24:         }
   25:  
   26:         protected override void CreateChildControls()
   27:         {
   28:             base.CreateChildControls();
   29:         }
   30:     }
   31: }

After deploying this web part to my site collection, if I refresh the page or open the site using a new browser session, I'll get to see the date/time that I saw the first time I visited the page. This happens till the cache expiration time expires (in my case it was 3600 seconds). Now, that caching happens, I had to implement post cache substitution to not cache another web part that also returns the current date/time.

The actual code the renders the current date/time is the same, but it is rendered in a different mechanism. First, I added another class file to my current web part project and implemented the response substitution call back method. Sample code below:

    1: using System;
    2: using System.Collections.Generic;
    3: using System.Text;
    4: using System.Globalization;
    5: using System.IO;
    6: using System.Reflection;
    7: using System.Web;
    8: using System.Web.UI;
    9: using System.Web.UI.WebControls;
   10:  
   11: namespace PCSWebPart
   12: {
   13:     public abstract class DontCachePlease
   14:     {
   15:         private ConstructorInfo _writerConstructor;
   16:         private HttpContext _context;
   17:  
   18:         protected DontCachePlease()
   19:         {}
   20:  
   21:         protected HttpContext Context
   22:         {get { return _context; }}
   23:  
   24:         public void Render(HttpContext context, HtmlTextWriter writer)
   25:         {
   26:             if (context == null)
   27:                 throw new ArgumentNullException("context");
   28:             if (writer == null)
   29:                 throw new ArgumentNullException("writer");
   30:             Type writerType = writer.GetType();
   31:             Type[] constructorArgs = new Type[] { typeof(TextWriter) };
   32:             _writerConstructor = writer.GetType().GetConstructor(constructorArgs);
   33:             if (_writerConstructor == null)
   34:                 throw new InvalidOperationException("The HtmlTextWriter does not have a public constructor taking in a TextWriter");
   35:             HttpResponseSubstitutionCallback subCallback = new HttpResponseSubstitutionCallback(this.RenderCallback);
   36:             context.Response.WriteSubstitution(subCallback);
   37:         }
   38:  
   39:         protected abstract void Render(HtmlTextWriter writer);
   40:  
   41:         private string RenderCallback(HttpContext context)
   42:         {
   43:             StringWriter baseWriter = new StringWriter(CultureInfo.CurrentCulture);
   44:             HtmlTextWriter writer = (HtmlTextWriter)_writerConstructor.Invoke(new object[] { baseWriter });
   45:             try
   46:             {
   47:                 _context = context;
   48:                 Render(writer);
   49:             }
   50:             finally
   51:             {
   52:                 _context = null;
   53:             }
   54:             return baseWriter.ToString();
   55:         }
   56:     }
   57: }

And then, I made the web part render the date/time using the call back method exposed off of this class.  Sample code below:

    1: using System;
    2: using System.Runtime.InteropServices;
    3: using System.Web.UI;
    4: using System.Web.UI.WebControls.WebParts;
    5: using System.Xml.Serialization;
    6:  
    7: using Microsoft.SharePoint;
    8: using Microsoft.SharePoint.WebControls;
    9: using Microsoft.SharePoint.WebPartPages;
   10:  
   11: namespace PCSWebPart
   12: {
   13:     [Guid("<random guid>")]
   14:     public class PCSWebPart : System.Web.UI.WebControls.WebParts.WebPart
   15:     {
   16:         public PCSWebPart()
   17:         {
   18:             this.ExportMode = WebPartExportMode.All;
   19:         }
   20:  
   21:         protected override void Render(HtmlTextWriter writer)
   22:         {
   23:             ShowTime st = new ShowTime();
   24:             st.Render(Context, writer);
   25:         }
   26:     }
   27:  
   28:     class ShowTime : DontCachePlease
   29:     {
   30:         public ShowTime()
   31:         {}
   32:  
   33:         protected override void Render(HtmlTextWriter writer)
   34:         { writer.WriteLine(DateTime.Now.ToString()); }
   35:     }   
   36: }

And that's it! I was able to see it working in the UI once I deployed this web part to my collaboration portal site.  Every time, I request for the page where I have test web parts loaded (1 using post caching substitution and the other without it), I could see the date/time value in the web part that does not use post caching substitution does not change till the cache expiration time I set, whereas the other show different date/time on every page request.  A visual representation of one my test instance below:

image

Hope this tip was helpful!!!