Advanced donut caching: using dynamically loaded controls
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.
Problem
If you look at Scott’s example it is pretty plain and easy by its output. All it does is it writes out current user name as string. Here are examples of my login area for anonymous and authenticated users:
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;
string controlName;
if (user != null)
controlName = "AuthenticatedLogInControl.ascx";
else
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))
{
ctl.DataBind();
ctl.RenderControl(htmlWriter);
}
return output.ToString();
}
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.
Conclusion
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.
Nice! Although I really think that the LoginView control should have this behaviour by default, you delivered a good solution to this framework flaw.
very innovative trick !!!
Nikunj Dhawan
I not yet manage this technique. Can you advise me other websites about this?
Thanks for your information, very useful for beginners like me.
Damn – I was prepared this day could kill my hair in frustration.. and then I saw this.
THANKS.
Pingback:How to avoid the exception “Substitution controls cannot be used in cached User Controls or cached Master Pages.” | Gunnar Peipman - Programming Blog