29 October 2007

The right way of accessing Master page properties from a child page

Master pages are a superbe idea. But sometimes you need to access Master Page properties from the child page. Suppose, for instance, you have a button on the Master Page with a text that you want to be able to set:
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="MyDemoMaster.master.cs" Inherits="MasterPageDemo.MyDemoMaster" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:button id="btnWithText" runat="server" text="Button">
         <asp:contentplaceholder id="ContentPlaceHolder1" runat="server">
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>
In the Master Page, you can create a property like this:
public string ButtonText
{
   get { return this.btnWithText.Text;  }
   set { this.btnWithText.Text  = value; }
}
Unfortunately, if you try to find the property "Master.ButtonText" in the childpage, it won't be available since the "Master" property is of type System.Web.UI.Page, and that does not contain properties of your derived class "MyDemoMaster" This, of course, can be solved by changing the Page_Load of the child page like this:
protected void Page_Load(object sender, EventArgs e)
{
  MyDemoMaster m = Master as MyDemoMaster;
  m.ButtonText = "My button text";
}
Congratulations. Your button shows the right text. And you have succeeded into locking your child page to a single master. Your web page will not run when you try to use another master page. Go back to programming class 101 and you don't get any cookies today ;-) The right solution is: create an interface like this:
namespace MasterPageDemo
{
    public interface IButtonText
    {
        string ButtonText { get;set;}
    }
}
Open the code behind file of your Master Page and let it implement the interface:
public partial class MyDemoMaster : System.Web.UI.MasterPage, IButtonText
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }

    public string ButtonText
    {
        get { return this.btnWithText.Text;  }
        set { this.btnWithText.Text  = value; }
    }
}
And then you make the Page_Load of the child page like this
protected void Page_Load(object sender, EventArgs e)
{
  IButtonText m = Master as IButtonText ;
  if( m != null ) m.ButtonText = "My button text";
}
Now the Master Page and Child Page are no longer coupled by name. Any Master Page implementing the IButtonText interface may be used as a Master Page for your child. Checking if it can be casted by testing m != null is a nice encore.

8 comments:

Anonymous said...

Excellent, Thanks!

Anonymous said...

It's a cool idea to implement interface handeling the master page property. However, we will be still running into issues if a page's template is a nested master page, any ideas on how to deal with this?

Joost van Schaik said...

I would suggest implementing the interface on both outer and inner masterpage. The nested master page's property tries to cast its master page in turn to the same interface, ad ininitum, till the top master page finally handles the property properly.

Anonymous said...

I converted it to vb but keep getting "Microsoft JScript runtime error: 'WebServiceDemo' is undefined"

I dont think my code is wrong, but its not working. Can you please provide a vb.net version of this project.

Joost van Schaik said...

@anonymous Are you sure you haven't posted this to the wrong posting? There is no "WebServiceDemo" in this posting

wbrproductions said...

awesome apples, I feel 15% less dumb after implementing this. Thanks!

Unknown said...

"Unfortunately, if you try to find the property "Master.ButtonText" in the childpage, it won't be available since the "Master" property is of type System.Web.UI.Page"

Very nice article, little clarification: the "Master"
property by default is of type Ssytem.Web.UI.UserControl

Unknown said...

I love this article. I always felt like putting a property in a MasterPage makes sense, but didn't like the idea of coupling the MasterPage to the child pages. This is an elegant, correct (OOP-wise) solution! Great share.