Performance Optimizations Part 2 – Cache Substitution Controls

Performance Optimizations Part 2 – Cache Substitution Controls

March 16, 2009 0 Comments

The content you're reading is getting on in years
This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.

In the previous article “Performance Optimizations - Why Output Cache is Important” we saw a brief overview of output caching and how it can help overall performance and experience of our sites. We also narrowed down possible problems that output caching may impose in certain scenarios.

In this article we will talk about cache substitution: what problems it solves, how it works, how Sitefinity makes substitution implementations easier and when to use it.


What is cache substitution? 

Cache substitution allows us to inject HTML into already cached output at predefined points. This way we can spare CPU time by rendering only small parts of the page that need frequent updates.


How it works?

ASP.Net provides HttpResponse.WriteSubstitution method that accepts HttpResponseSubstitutionCallback delegate as parameter. The signature of the delegate is: string HttpResponseSubstitutionCallback(HttpContext). This method allows us to register insertion points with the response output. Before the output is send to the client, all registered callbacks are called and the returned strings are inserted into the output stream at the corresponding insertion points. Pretty neat, but it is not so straightforward when and how to do the substitution. The delegates are called way too early in the request / response lifecycle and therefore there is no session state, page, controls or anything as we know it the regular page request. In most cases, in the delegates you will just read some data and return a string with hardcoded HTML formatting and that makes perfect sense as all this is intended for maximum performance.


How Sitefinity extends cache substitution?

Sitefinity provides two base classes
CacheSubstitutionUserControl and
CacheSubstitutionCompositeControl


These controls automate the registration and rendering process. For the sake of simplicity, from now on I will talk about CacheSubstitutionUserControl only but everything applies to CacheSubstitutionCompositeConrtol as well.

The control provides three page modes that define the control’s behavior. We will see examples and examine what are the pros and cons for each of the modes. For that purpose I picked a very common scenario. Actually there will be two scenarios which are slightly different, that will demonstrate all possible ways to use these controls.


In the first scenario, we have a page that displays the user name of the user making the current request in the header of the page. If the current request is made by unauthenticated user, instead of the name we will display a link to the login page. The only difference in the second scenario will be that instead of link to the login page, we will display a login form directly in the header of the page.


Let’s use Telerik International University sample project for this demo. The first example will demonstrate the control using PageMode set to “None”. Using the control in this mode is almost identical as directly using HttpResponse.WriteSubstitution method except that it allows us to encapsulate the implementation in the control and use it on Sitefinity pages.


1. Create new folder in UserControls folder named CacheSubstitutionDemo.
2. Create new user control named UserInfo.ascx.
3. Open the code behind and change the class to inherit form CacheSubstitutionUserControl instead of UserControl.
4. Copy the methods form the sample code below to the body of your class: 

using System;  
using System.Web;  
using System.Web.Security;  
using Telerik.Cms.Web.UI;  
using Telerik.Web;  
 
public partial class UserControls_Login_UserInfo : CacheSubstitutionUserControl  
{  
    public override SubstitutionPageMode PageMode  
    {  
        get 
        {  
            return SubstitutionPageMode.None;  
        }  
    }  
 
    public override HttpResponseSubstitutionCallback SubstitutionCallback  
    {  
        get 
        {  
            return GetUserInfo;  
        }  
    }  
 
    public static string GetUserInfo(HttpContext context)  
    {  
        if (context.Request.IsAuthenticated)  
        {  
            return String.Format("<strong style=\"float:left;\">{0}</strong>", Membership.GetUser().UserName);  
        }  
        else 
        {  
            return String.Format("<a href=\"{0}\" style=\"float:left;\">Log in</a>", UrlPath.ResolveUrl("~/Sitefinity/Login.aspx"));  
        }  
    }  
}  
 

 
5. Register your control with Sitefinity’s Control Toolbox in web.config as shown below:

<toolboxControls> 
   <clear /> 
   <add name="User Info" section="Most popular" url="~/UserControls/CacheSubstitutionDemo/UserInfo.ascx" /> 
   ... 

and set default caching provider to ASPNET

<framework> 
      <caching defaultProvider="ASPNET">  
        <providers> 
          <add name="memoryCache" type="Telerik.Caching.MemoryCachingProvider, Telerik.Framework"/>  
          <add name="ASPNET" type="Telerik.Caching.AspNetCachingProvider, Telerik.Framework" duration="120" slidingExpiration="true"/>  
        </providers> 
        <cacheDependency mode="InMemory"/>  
      </caching> 
  ... 

6. Run the web site, navigate to ~/Sitefinity/Admin/Templates.aspx and open TIU3Column template.
7. Drop the control in MainNavigation place holder.
8. Save the changes
9. Got to Pages section, select Home page, go to Properties and turn on caching.
10. Save the changes and examine the home page on the public side.

 

Note that cache substitution works only with ASPNET caching provider.


In this example we are just exposing static HttpResponseSubstitutionCallback delegate in which we do the entire implementation. The base control registers the delegate with the response for us. Note that if you declare any controls in the .ascx file, they won’t be rendered. Our control will display only whatever our GetUserInfo method returns. This is because after the initial request, no instance of our control or page will be created. Only our static method will be invoked. Obviously this is very efficient method as the only overhead for the response will be whatever we do inside the static method. However this method will be very inconvenient if we have to do something more complex which will become particularly difficult to maintain later. That’s why CacheSubstitutionUserControl provides the two other modes.


In the second example we will see how Partial mode woks and how it defers form the previous.

1. In the CacheSubstitutionDemo folder create new user control named UserInfoPartial.ascx
2. Replace the declarations in the .ascx file with the sample below.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="UserInfoPartial.ascx.cs" Inherits="UserControls_CacheSubstitutionDemo_UserInfoPartial" %> 
<asp:PlaceHolder ID="loginFormHolder" runat="server">  
    <asp:HyperLink ID="loginForm" NavigateUrl="~/Sitefinity/Login.aspx" Text="Log In" runat="server"></asp:HyperLink> 
</asp:PlaceHolder> 
<asp:PlaceHolder ID="userInfoHolder" runat="server">  
    <asp:Label ID="userInfo" runat="server"></asp:Label> 
</asp:PlaceHolder> 
 

3. Replace the code in the code behind file from the sample below.

using System.Web;  
using System.Web.Security;  
using System.Web.UI;  
using Telerik.Cms.Web.UI;  
 
public partial class UserControls_CacheSubstitutionDemo_UserInfoPartial : CacheSubstitutionUserControl  
{  
    protected override void Render(HtmlTextWriter writer)  
    {  
        bool isAuthenticated = HttpContext.Current.Request.IsAuthenticated;  
        this.userInfoHolder.Visible = isAuthenticated;  
        this.loginForm.Visible = !isAuthenticated;  
        if (isAuthenticated)  
            this.userInfo.Text = Membership.GetUser().UserName;  
 
        base.Render(writer);  
    }  
 
    public override SubstitutionPageMode PageMode  
    {  
        get 
        {  
            return SubstitutionPageMode.Partial;  
        }  
    }  
}  
 

4. Do the steps from 5 to 10 from the previous example.


As you have probably noticed, this control does exactly the same what the previous one does but allows us to easily maintain its layout. What happens behind the scenes is this: Sitefinity registers a callback just like the one in the first example. When the call back is invoked Sitefinity creates an instance of our control, renders it outside the current HttpContext and then returns the rendered HTML as result of the callback. This is little bit more work but still very efficient as only our controls is rendered. The drawback of this method is that the control is rendered outside of the context and therefore no events or control state is handled. That’s why we are overriding Render method to do our logic instead of the usual Page_Load event.


The third mode allows us to have fully functional control with events, control state and postbacks. Of course it adds a bit more to the overhead.


1. Crate new user control and name it LoginForm.ascx.
2. Replace the declarations in the .ascx file with the sample below.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="LoginForm.ascx.cs" Inherits="UserControls_CacheSubstitutionDemo_LoginForm" %> 
<div id="LoginFormHolder" runat="server">  
    <asp:Literal ID="ErrorMessage" runat="server">  
        <strong style="color:Red;">Wrong user name or password!</strong>&nbsp;&nbsp;&nbsp;  
    </asp:Literal> 
    <asp:Label ID="UserNameLabel" Text="User Name" AssociatedControlID="UserName" runat="server"></asp:Label>&nbsp;  
    <asp:TextBox ID="UserName" runat="server"></asp:TextBox>&nbsp;&nbsp;&nbsp;  
    <asp:Label ID="PasswordLabel" Text="Password" AssociatedControlID="Password" runat="server"></asp:Label>&nbsp;  
    <asp:TextBox ID="Password" TextMode="Password" runat="server"></asp:TextBox>&nbsp;  
    <asp:Button ID="Login" Text="Log in" runat="server" OnClick="Login_Click" /> 
</div> 
<div id="UserInfoHolder" runat="server">  
    <span>Welcome&nbsp;  
    <strong> 
        <asp:Literal ID="UserInfo" runat="server"></asp:Literal> 
    </strong>&nbsp;  
    <asp:LinkButton ID="LogOut" Text="Log Out" OnClick="LogOut_Click" runat="server"></asp:LinkButton></span>  
</div> 
 

3. Replace the code in the code behind file from the sample below.

using System;  
using System.Web;  
using System.Web.Security;  
using System.Web.UI;  
using Telerik.Cms.Web.UI;  
 
public partial class UserControls_CacheSubstitutionDemo_LoginForm : CacheSubstitutionUserControl  
{  
    public override SubstitutionPageMode PageMode  
    {  
        get 
        {  
            return SubstitutionPageMode.Full;  
        }  
    }  
 
    protected void Page_Load(object sender, EventArgs e)  
    {  
        if (!this.IsPostBack)  
            this.SetControls(false);  
    }  
 
    protected void Login_Click(object sender, EventArgs e)  
    {  
        if (Membership.ValidateUser(this.UserName.Text, this.Password.Text))  
        {  
            FormsAuthentication.SetAuthCookie(this.UserName.Text, false);  
            this.Response.Redirect(this.Request.RawUrl, false);  
        }  
        else 
        {  
            this.SetControls(true);  
        }  
    }  
 
    protected void LogOut_Click(object sender, EventArgs e)  
    {  
        FormsAuthentication.SignOut();  
        this.Response.Redirect(this.Request.RawUrl, false);  
    }  
 
    private void SetControls(bool error)  
    {  
        bool isAuthenticated = HttpContext.Current.Request.IsAuthenticated;  
        this.UserInfoHolder.Style.Add(HtmlTextWriterStyle.Display, isAuthenticated ? "block" : "none");  
        this.LoginFormHolder.Style.Add(HtmlTextWriterStyle.Display, isAuthenticated ? "none" : "block");  
        this.ErrorMessage.Visible = error;  
        if (isAuthenticated)  
            this.UserInfo.Text = Membership.GetUser().UserName;  
    }  
 
    protected override void Render(HtmlTextWriter writer)  
    {  
        this.Page.ClientScript.RegisterForEventValidation(this.Login.ClientID);  
        this.Page.ClientScript.RegisterForEventValidation(this.LogOut.ClientID);  
        base.Render(writer);  
    }  
}  
 

4. Do the steps from 5 to 10 from the previous example.

progress-logo

The Progress Guys

View all posts from The Progress Guys on the Progress blog. Connect with us about all things application development and deployment, data integration and digital business.

Comments
Comments are disabled in preview mode.
Topics
 
 
Latest Stories in
Your Inbox
Subscribe
More From Progress
d12fcc0bdb669b804e7f71198c9619a7
5 Questions Automakers Should Ask to Improve Asset Uptime
Download Whitepaper
 
SF_MQ_WCM
2018 Gartner Magic Quadrant Web Content Management (WCM)
Download Whitepaper
 
What-Serverless-Means-For-Enterprice-Apps-Kinvey
What Serverless Means for Enterprise Apps
Watch Webinar