Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to manipulate master page controls in ASP.NET when output caching is enabled

0.00/5 (No votes)
21 Aug 2012 1  
This short article shows how to manipulate master page controls in ASP.NET when output caching is enabled.

Table of Contents

  • Introduction
  • Most easy way out
  • What will happen when control has output cache defined?
  • What is the solution then?
  • A good suggestion
  • Problem: Adding control back in Page.Controls creates server error
  • Problem: Setting other properties of the control other than visibility
  • Conclusion
  • References

Development Platform

  • Visual Studio 2010
  • Microsoft .NET Framework 4.0
  • IIS 5.1~7

Introduction

In this short article we are going to see how we can use some smart tricks for manipulating controls in the master page of an ASP.NET website from a content page. The topic seems a little bit lame and should be pretty straightforward but I wouldn’t be discussing with the community unless it has a very unexpected turn.

Here is the situation, we have an ASP.NET 4.0 web application with a single Master page. In some cases we need to hide the master page’s controls, in particular the header and footer.

To demonstrate the ideas and story I have created a simple ASP.NET 4.0 web application in Visual Studio. The Solution Explorer components are given above as a screenshot, nothing special, just taken after creating the project.

“Site.Master” the site layout HTML given below:

<body>
    <form runat="server">
        <div class="page">
            <uc1:Header ID="Header1" runat="server" />
            <div class="main">
                <asp:ContentPlaceHolder ID="MainContent" runat="server" />
            </div>
            <div class="clear">
            </div>
        </div>
        <uc2:Footer ID="Footer1" runat="server" />
    </form>
</body>

Looks pretty innocent, right? In fact most of our website templates have a header, main-content, footer in a generic style. Now then the requirement is to hide the header and footer in one of the content pages, in fact in a couple of pages. Those pages are report pages and it is not necessary to show the header and footer as those pages can be printed.

Most easy way out

Most easy way out is just use the FindControl method in the page.master object and then simply hide them. We can also cast them to a custom control and then set the properties. This will work in 99% cases and is really simple. This can be found in net with a Google search, and the first result will do the job. Below is the code for the most straightforward way.

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        var masterPage = Master;
        if (masterPage != null)
        {
            masterPage.FindControl("Header1").Visible = false;
            masterPage.FindControl("Footer1").Visible = false;
        }
    }
}

Yes, just like that, and our site’s header and footer will open the rules of visibility. Now we are here because we have a high performance website and it has no view state, no session, whatsoever. All login and security measures depend on cookies and each request.

Moreover we have output caching enabled on the header and footer for “9000”.

What will happen when control has output cache defined?

I am sure all of you know that you can cache a page, user control's output cache, it’s a standard ASP.NET attribute and can be added in any page or user control to cache the output for an object for a particular period of time with some parameters or customization. To do so we have to add <%@ OutputCache Duration="9999" VaryByParam="none" %> to the control or page declaration in HTML. To know more about it, please visit http://msdn.microsoft.com/en-us/library/hdxfb6cy(v=vs.71).aspx.

What happens to the above code when we run the application in Debug mode? The page works fine for the first time. What would happen if we try to access it again? Just hit Enter in the browser or refresh the browser page, crap! It is not working, showing a server error and it says the master page does not have this control and eventually throws a server error.

If you check if the control exists or not, the first time it will return yes, the second time it will say it does not exist. It is normal and happens because the controls that we are trying to modify are cached, and hence can’t be accessed like that.

What is the solution then?

The solution is to find the cached control and then work on that control and finally add it to the control again so that whatever we have changed is reflected on the page's final output. Below given is the code segment for this. This time we have modified the code and kept it in the master page, and just invoked a method from the content page.

Master page’s code

using System;
using System.Web.UI;
using MasterPageControlExample.Controls;

namespace MasterPageControlExample
{
public partial class SiteMaster : MasterPage
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }

    public void DisplayHeader(bool value)
    {
        if(Header1!=null)
            Header1.Visible = value;
        else
        {
            var cachedHeader = (PartialCachingControl) Page.LoadControl(@"Controls\Header.ascx");
            Page.Controls.Add(cachedHeader);
            if(cachedHeader.CachedControl !=null)
            {
                var header = cachedHeader.CachedControl as Header;
                if (header != null) header.Visible = value;
            }
        }
    }

    public void DisplayFooter(bool value)
    {
        if (Footer1 != null)
            Footer1.Visible = value;
        else
        {
            var cachedFooter = (PartialCachingControl)LoadControl(@"Controls\Footer.ascx");
            Page.Controls.Add(cachedFooter);
            if (cachedFooter.CachedControl != null)
            {
                var footer = cachedFooter.CachedControl as Footer;
                if (footer != null) footer.Visible = value;
            }
        }
    }
}
}

Content page’s code

using System;

namespace MasterPageControlExample
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            var masterPage = Master;
            if (masterPage != null)
            {
                var siteMaster = (masterPage as SiteMaster);
                if (siteMaster != null) siteMaster.DisplayHeader(false);
            }
        }
    }
}

In the above master page code we first check if the control exists or not by simply checking for null. The first time it won't be null and can directly assign the properties. The second and subsequent times until the control is created once again when the cache expires, the else block of the code will be executed.

In the else block of the DisplayHeader or DisplayFooter method, we have loaded the control. This time the Page.LoadControl method will return a PartialCachingControl object. It has to be again added to Page.Controls to be able to be accessed from the CachedControl property of the PartialCachingControl object. Finally we add the required properties value.

Now this code will work on 100% case as we have over came the problem with output cache as well.

A good suggestion

Even if you are a beginner you probably know it but still sharing this as some of us might not have implemented it. I am talking about the PageBase and ControlBase objects for an ASP.NET site. The idea is just to reuse some repeating code and other cross cutting concerns if we have any in our page life cycle. Here I suggest that you create a BasePage.cs class where you will encapsulate code for displaying header and footer, and in the content page, you will just call these methods, and in that case your common code will all be in one place.

Below is the code for BasePage.cs:

BasePage’s code

namespace MasterPageControlExample.Objects
{
    public class BasePage:System.Web.UI.Page
    {
        protected void DisplayHeader(bool value)
        {
            var masterPage = Master;
            if (masterPage != null)
            {
                var siteMaster = (masterPage as SiteMaster);
                if (siteMaster != null) siteMaster.DisplayHeader(value);
            }
        }

        protected void DisplayFooter(bool value)
        {
            var masterPage = Master;
            if (masterPage != null)
            {
                var siteMaster = (masterPage as SiteMaster);
                if (siteMaster != null) siteMaster.DisplayFooter(value);
            }
        }
    }
}

Content page’s code

using System;
using MasterPageControlExample.Objects;

namespace MasterPageControlExample
{
    public partial class _Default : BasePage
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            DisplayHeader(false);
        }
    }
}

This looks compact and organized now, when we manipulate some cached control, it tends to create some inherent problems, some of the problems are discussed below.

Problem: Adding a control back in Page.Controls creates a server error

We can’t add the cached control in the Page.Controls collection, since there is a good chance that controls such as the header and footer will have some tags that have server controls and that’s going to create problems while adding those controls in to the page.

Result of adding header back to Page.Controls

Then what is the solution? We have to add in a placeholder where for this master page, the markup needs to change so that header and footer will be declared declaratively inside a place holder.

Modified master page code

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" 
Inherits="MasterPageControlExample.SiteMaster" %>

<%@ Register Src="Controls/Header.ascx" TagName="Header" TagPrefix="uc1" %>
<%@ Register Src="Controls/Footer.ascx" TagName="Footer" TagPrefix="uc2" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
    <title></title>
    <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
    <asp:ContentPlaceHolder ID="HeadContent" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body>
    <form runat="server">
    <div class="page">
        <asp:PlaceHolder ID="PlaceHolderHeader" runat="server">
            <uc1:Header ID="Header1" runat="server" />
        </asp:PlaceHolder>
        <div>
            <h2>
                <a href="Default.aspx">Default.aspx</a><br />
                <a href="About.aspx">About.aspx</a><br />
                <a href="Report.aspx">Report.aspx</a><br />
            </h2>
        </div>
        <div class="main">
            <asp:ContentPlaceHolder ID="MainContent" runat="server" />
        </div>
        <div class="clear">
        </div>
    </div>
    <asp:PlaceHolder ID="PlaceHolderFooter" runat="server">
        <uc2:Footer ID="Footer1" runat="server" />
    </asp:PlaceHolder>
    </form>
</body>
</html>

Note that in the above code we added two place holders to hold the header and footer, and the header and footer are user controls added inside the place holder. Now if our intention is just to modify the visibility status there is a very easy way out again.

Here is the situation, we have three content pages:

  • Default page [where we want to display both header and footer]
  • About page [where we want to display only header and hide footer]
  • Report page [where we want to hide both header and footer]

Content page’s code in this scenario

//first page
public partial class _Default : BasePage
{
    protected void Page_Load(object sender, EventArgs e)
    {
        DisplayHeader(true);
        DisplayFooter(true);
    }
}
//second page
public partial class About : BasePage
{
    protected void Page_Load(object sender, EventArgs e)
    {
        DisplayHeader(true);
        DisplayFooter(false);
    }
}
//third page
public partial class Report : BasePage
{
    protected void Page_Load(object sender, EventArgs e)
    {
        DisplayHeader(false);
        DisplayFooter(false);
    }
}

Master page’s code

public void DisplayHeader(bool value)
{
    if(Header1!=null)
        Header1.Visible = value;
    else
    {
        Page.LoadControl(@"Controls\Header.ascx");
        var cachingControl = PlaceHolderHeader.Controls[0] as StaticPartialCachingControl;
        if (cachingControl != null) cachingControl.Visible = value;
    }
}

public void DisplayFooter(bool value)
{
    if (Footer1 != null)
        Footer1.Visible = value;
    else
    {
        LoadControl(@"Controls\Footer.ascx");
        var cachingControl = PlaceHolderFooter.Controls[0] as StaticPartialCachingControl;
        if (cachingControl != null) cachingControl.Visible = value;
    }
}

Note that in this case I have cast control[0] as the place holder as we have only one control inside the placeholder. Also note that you can’t set the custom properties here. As it’s a StaticPartialCachingControl.

Problem: Setting other properties of the control other than visibility

Well this is the final part of the article in this section. We want to set other properties of the cached control, unfortunately it is not that easy and there is not an easy way out for setting properties in a cached control. Rather we have to load the control and then add it back in the Controls collection, but before changing the desired property.

The intention is to change the site title in the About.aspx page. I am going to skip the rest of the details and list the code for the master page. That will be self explanatory.

Master page’s code for setting site title

public void SetSiteTitle(string siteTitle)
{
   if (Header1 != null)
       Header1.SiteTitle = siteTitle;
   else
   {
       var cachedHeader = (PartialCachingControl)LoadControl(@"Controls\Header.ascx");
       PlaceHolderHeader.Controls.Clear();
       PlaceHolderHeader.Controls.Add(cachedHeader);
       if (cachedHeader.CachedControl != null)
       {
           var header = cachedHeader.CachedControl as Header;
           if (header != null) header.SiteTitle = siteTitle;
       }
   }
}

There are a lot of things that I want to list about this tiny little article but keeping away some things for the future. I hope I would get good feedback for this article.

Conclusion

In this short article we have seen a particular problem’s most easy way out, followed by what will happen when caching is enabled for a control, and finally some tips and tricks to make it perfect. The knowledge is available in the web and can be found easily with a single search in any search engine, however for beginners it’s a panic situation, and I have tried to organize the ideas sequentially so that it make sense in first read for an absolute beginner.

References

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here