Sometimes the simplest things in ASP.NET 2.0 turn out to be the hardest things to implement. One example is trying to have a shared property across all your web pages. For example, let's say that you want to have a User object that is on all of your pages and that object be initialized on Page_Load. One way to do this is to have the same code in all your pages, like:
Example 1:
---- page1.aspx.cs
public partial class Page1 : System.Web.UI.Page
{
protected String _UserName;
protected void Page_Load(object sender, EventArgs e)
{
_UserName = Session["UserName"].ToString();
}
}
---- page1.aspx.cs
public partial class Page2 : System.Web.UI.Page
{
protected String _UserName;
protected void Page_Load(object sender, EventArgs e)
{
_UserName = Session["UserName"].ToString();
}
}
However, this is really repetitious, so instead let us subclass the System.Web.UI.Page class and create our own class to inherit all our pages from like:
Example 2:
---- ParentPage.cs
public class ParentPage : System.Web.UI.Page
{
protected String _UserName;
public ParentPage()
{
this.Load += new System.EventHandler(this.Page_Load);
}
protected void Page_Load(object sender, EventArgs e)
{
_UserName = Session["UserName"].ToString();
}
}
---- page1.aspx.cs
public partial class Page1 : ParentPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
---- page2.aspx.cs
public partial class Page1 : ParentPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
Note that we need to add the Page_Load method in the ParentPage.cs to the loading event handler so that the method will get called when the page loads.
Now we just need to use the _UserName in either of our pages, even though it is a protected member of ParentPage, the pages that subclass from that page can reach the member. However, other objects can change _UserName. This we get from inheritance.
Let's use the _UserName in the first page when it loads.
Example 3:
---- page1.aspx.cs
public partial class Page1 : ParentPage
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(_UserName);
}
}
Here is where we run into the first problem. Because of the way that ASP.NET works you don't know which Page_Load will be called first. If the Page_Load for the Page1 class is called first, then _UserName will not be initialized yet. If the Page_Load for PageParent class is called first everything will work find. To solve this issue, all variables that we need for the pages need to be configured in the OnInit method, like this:
Example 4:
---- ParentPage.cs
public class ParentPage : System.Web.UI.Page
{
protected String _UserName;
protected override void OnInit(EventArgs e)
{
// WWB: Allow The Base Class To Intialize First
base.OnInit(e);
_UserName = Session["UserName"].ToString();
}
}
---- page1.aspx.cs
public partial class Page1 : ParentPage
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(_UserName);
}
}
This gives us control of the load order, since we are overriding a method, and not signing up for an event. If the page needs to use OnInit it can do so like this:
Example 5:
---- ParentPage.cs
public partial class Page1 : ParentPage
{
protected override void OnInit(EventArgs e)
{
// WWB: Allow The Base Class To Intialize First
base.OnInit(e);
}
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(_UserName);
}
}
Notice that Page1::OnInit class is calling ParentPage::OnInit, this allows us to "chain" the initialization. You could even use _UserName variable after the base.OnInit(e); if you wanted.
One of the very nice features of ASP.NET 2.0 is master pages, let's add a master page to Page1 project, like this:
Example 6:
---- page1.aspx.cs
<%@ Page Language="C#" MasterPageFile="~/MasterPage1.master" AutoEventWireup="true" CodeFile="Page1.aspx.cs" Inherits="Page1" %>
<asp:content id="Content" contentplaceholderid="ContentPlaceHolder1" runat="server">
<%Response.Write(_UserName);%>
</asp:content>
---- MasterPage1.master.cs
public partial class MasterPage1 : System.Web.UI.MasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
Now let's set the title of the master page to the name of user, first lets modify ParentPage so that we can get __UserName property, we can do this like this:
Example 7:
-- ParentPage.cs
public class ParentPage : System.Web.UI.Page
{
protected String _UserName;
public String UserName
{
get { return (_UserName); }
}
protected override void OnInit(EventArgs e)
{
// WWB: Allow The Base Class To Intialize First
base.OnInit(e);
_UserName = Session["UserName"].ToString();
}
}
Now all we have to do is modify the master page to set the title on page load. The code should look something like this:
Example 8:
--MasterPage1.master
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage1.master.cs" Inherits="MasterPage1" %>
<h1><asp:Label ID="lblTitle" runat=server></asp:Label></h1>
<asp:contentplaceholder id="ContentPlaceHolder1" runat="server">
</asp:contentplaceholder>
--- MasterPage1.master.cs
public partial class MasterPage1 : System.Web.UI.MasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
lblTitle.Text = Page.UserName;
}
}
However, this will not compile, we get this error:
'System.Web.UI.Page' does not contain a definition for 'UserName'
This is because the 'System.Web.UI.Page' doesn't contain the UserName property, this property can only be found on the ParentPage class. So lets cast the Page property of the master page.
Example 9:
--- MasterPage1.master.cs
public partial class MasterPage1 : System.Web.UI.MasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
lblTitle.Text = ((ParentPage)Page).UserName;
}
}
However, if we do this we need to make sure that only pages that inherit from ParentPage class use this master page, which is too restrict for us, so let's rearrange so that the Page calls the master page and sets the title to the user name
Example 10:
--- MasterPage1.master.cs
public partial class MasterPage1 : System.Web.UI.MasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
public void SetTitle(String title)
{
lblTitle.Text = title;
}
}
---- page1.aspx.cs
public partial class Page1 : ParentPage
{
protected override void OnInit(EventArgs e)
{
// WWB: Allow The Base Class To Intialize First
base.OnInit(e);
}
protected void Page_Load(object sender, EventArgs e)
{
((MasterPage1)Master).SetTitle(_UserName);
}
}
This too requires a cast of the Master property, however this is much safer, because we know that the page is using Master1.master. Now it would be very nice to have ParentPage.cs set the title of the master Page.
Example 11:
---- ParentPage.cs
public class ParentPage : System.Web.UI.Page
{
protected String _UserName;
public String UserName
{
get { return (_UserName); }
}
protected override void OnInit(EventArgs e)
{
// WWB: Allow The Base Class To Intialize First
base.OnInit(e);
_UserName = Request["UserName"].ToString();
this.Load += new System.EventHandler(this.Page_Load);
}
protected void Page_Load(object sender, EventArgs e)
{
((MasterPage1)Master).SetTitle(_UserName);
}
}
---- page1.aspx.cs
public partial class Page1 : ParentPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
However when we compile this we get this error:
The type or namespace name 'MasterPage1' could not be found (are you missing a using directive or an assembly reference?)
This is a confusing error message. We shouldn't need a assembly reference since all the pages are in the same
project, and we are not missing a directive (#using) since all the pages are in the same namespace.
The issue is that anything in /app_code (like PartentPage.cs) is in a different assembly that knows
nothing about pages or master pages. Pages and master pages can "find" the assemblies
in app_code. However, any classes in /app_code can't reference the classes that make up the pages and the master pages.
The first idea is to move ParentPage.cs out of app_code, however this is impossible and doesn't work.
So how do we make this work? We need to subclass System.Web.UI.MasterPage, creating a ParentMasterPage
in app_code, like this:
Example 12:
---- ParentMasterPage.cs
public class ParentMasterPage : System.Web.UI.MasterPage
{
protected String _title;
public ParentMasterPage()
{
}
public void SetTitle(String title)
{
_title = title;
}
}
---- ParentPage.cs
public class ParentPage : System.Web.UI.Page
{
protected String _UserName;
public String UserName
{
get { return (_UserName); }
}
protected override void OnInit(EventArgs e)
{
// WWB: Allow The Base Class To Intialize First
base.OnInit(e);
_UserName = Request["UserName"].ToString();
this.Load += new System.EventHandler(this.Page_Load);
}
protected void Page_Load(object sender, EventArgs e)
{
((ParentMasterPage)Master).SetTitle(_UserName);
}
}
---- MasterPage1.master.cs
public partial class MasterPage1 : ParentMasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
lblTitle.Text = _title;
}
}
The ParentPage class can call the SetTitle method in ParentMasterPage class since both classes are both in app_code, instead of setting the asp:label in ParentMasterPage we set it to a property and any master pages that inherit from ParentMasterPage can access this variable. MasterPage1 class uses the _title property to set the label.
The finished project in Visual Studio 2005 looks like this: