Thursday, August 16, 2012

How to manipulating master page controls in asp.net, when output caching is enabled?

Introduction

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

image

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

image

To demonstrate the ideas and story I have created a simple asp.net 4.0 web application in visual studio, the solution explorer components is given above as screen shot, nothing special just taken after creating the project.

“Site.Master” the site layout html is given bellow:

<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, infect most of our web site template has header,main-content,footer generic style. Now then the requirement is to hide the header and footer in one of the content page, in fact in couple of pages, why those page are some kind of report page and its not necessary to show header and footer as those page can be printed.


Most easy way out


Most easy way out is just use find control method in page.master object and then simply hide them. we can also cast them to custom control and then set properties, this will work in 99% case and really simple. This can be found in net with one google only, and in first result will do the job. Bellow the code is given of the strait forward 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 web and it has not view state, no session, what so ever. All login and security measure depends on cookie and each request.


More over we have output caching enabled on 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 page, user control's  output cache, it’s a default asp.net attribute and can be added in any page and user control to cache output for a object for particular period of time with some parameter or customization. To do so we have to add “ <%@ OutputCache  Duration="9999" VaryByParam="none" %> “ in 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 happen to the above code when we run the application in debug mode? The page works fine for the first time it works fine great!. what would happen if it try to access it again?, just hit inter in browser or refresh the  browser page, crap! its not working, showing server error and it says master page does not have this control eventually throws server error.


image



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


What is the solution then?


Solution is we have to find the cached control and then work on that control finally add it to the control again so that, what ever we have changed reflected on the page final output. Bellow the code segment is given for this. This time we have modified the code and kept it in master page, and just invoked a method from 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 above master page’s code we fist checked if the control is exists or not by simply checking null, for the first time it wont be null and can directly assign the properties, for the second and subsequent times until the control is been created once again when the cache expires the else block of the code will be executed.


In else block of DisplayHeader or DisplayFooter method we have Loaded the control, this time this Page.LoadControl method will return PartialCachingControl object. It have to be again added to Page.Controls to be able to access from CachedControl property of PartialCachingControl object. Finally added 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 as some of us might not implemented it. I am talking about PageBase or ControlBase objects for asp.net site. The idea is just to reuse some repeating code and some other cross cutting concern if we have any in our page life cycle. Here I suggest that you create a BasePage.cs class where you will encapsulate the codes like display header and footer, and in content page you will just call this methods, in that case your code will in one place.


Bellow the code for BasePage.cs is given.


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 tend to create some inherent problems, some of the problems are discussed bellow.


problem: Adding control back in page.controls create server error


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


Result of adding header back to Page.controls


image


Then what is the solution? we have to add in a placeholder where for this master page markup need to change so that header and footer will be declared declaratively in side 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 above code added two place holder to hold header and footer, and the header and footer which is user control added inside the place holder, now then 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 page



  1. Default page [where we want to display both header and footer]

  2. About page [where we want to display only header and hide footer]

  3. 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 the control[0] as the place holder as we have only one control inside the place holder,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 its not that much easy and there is not easy way out for setting properties in a cached control rather we have to load control and then add it back in control collection, but before change the desired property.


The intention is to change site title in About.aspx page. I am going to skip the rest of the other detail and list the code for 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 lots of thing that I want to list about this tiny little article but keeping some thing for the future. I hope I would get good feedback about this article.


Conclusion


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


references


No comments:

Post a Comment