Yesterday I solved one caching problem with local community portal. I enabled output cache on SharePoint Server 2007 to make site faster. Although caching works fine I needed to do some additional work because there are some controls that show different content to different users. In this example I will show you how to use “donut caching” with user controls – powerful way to drive some content around cache.
About donut caching
Donut caching means that although you are caching your content you have some holes in it so you can still affect the output that goes to user. By example you can cache front page on your site and still show welcome message that contains correct user name.
To get better idea about donut caching I suggest you to read ScottGu posting Tip/Trick: Implement "Donut Caching" with the ASP.NET 2.0 Output Cache Substitution Feature.
Basically donut caching uses ASP.NET substitution control. In output this control is replaced by string you return from static method bound to substitution control. Again, take a look at ScottGu blog posting I referred above.
It is clear that outputting mark-up for these views as string is pretty lame to implement in code at string level. Every little change in design will end up with new version of controls library because some parts of design “live” there.
Solution: using user controls
I worked out easy solution to my problem. I used cache substitution and user controls together. I have three user controls:
- LogInControl – this is the proxy control that checks which “real” control to load.
- AnonymousLogInControl – template and logic for anonymous users login area.
- AuthenticatedLogInControl – template and logic for authenticated users login area. This is the control we render for each user separately because it contains user name and user profile fill percent.
Anonymous control is not very interesting because it is only about keeping mark-up in separate file. Interesting parts are LogInControl and AuthenticatedLogInControl.
Creating proxy control
The first thing was to create control that has substitution area where “real” control is loaded. This proxy control should also be available to decide which control to load. The definition of control is very primitive.
<%@ Control EnableViewState="false" Inherits="MyPortal.Profiles.LogInControl" %>
<asp:Substitution runat="server" MethodName="ShowLogInBox" />
But code is a little bit tricky. Based on current user instance we decide which login control to load. Then we create page instance and load our control through it. When control is loaded we will call DataBind() method. In this method we evaluate all fields in loaded control (it was best choice as Load and other events will not be fired). Take a look at the code.
public static string ShowLogInBox(HttpContext context)
var user = SPContext.Current.Web.CurrentUser;
if (user != null)
controlName = "AuthenticatedLogInControl.ascx";
controlName = "AnonymousLogInControl.ascx";
var path = "~/_controltemplates/" + controlName;
var output = new StringBuilder(10000);
using(var page = new Page())
using(var ctl = page.LoadControl(path))
using(var writer = new StringWriter(output))
using(var htmlWriter = new HtmlTextWriter(writer))
When control is bound to data we ask to render it its contents to StringBuilder. You need to implement your data binding logic in DataBind() method override. Now we have the output of control as string and we can return it from our method. Of course, notice how correct I am with resources disposing. :)
The method that returns contents for substitution control is static method that has no connection with control instance because hen page is read from cache there are no instances of controls available.
As you saw it was not very hard to use donut caching with user controls. Instead of writing mark-up of controls to static method that is bound to substitution control we can still use our user controls.