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
public partial class _Default : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
DisplayHeader(true);
DisplayFooter(true);
}
}
public partial class About : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
DisplayHeader(true);
DisplayFooter(false);
}
}
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