Visual Inheritance and ASP.NET WebForms

Nov 09, 2011
Diego Vergara

Problem: There is a base class BasePage.cs which inherits from System.Web.UI.Page. This base class is responsible for defining common properties, setting default values, base rendering logic, cross-cutting concerns logic, among other common functionalities. There is also a master page MasterPage.master which provides the inherited page with the common visual elements to be shared across the site. This is how the inheritance diagram looks like:

inheritance

 

In some cases MyPage should render some extra controls depending on the purpose of the page. For instance, sometimes MyPage is used for transactions that need no authorization and sometimes for transactions that need authorization. For this purpose there is a user control UserControl.ascx that is rendered for performing authorization. In addition, MyPage should be able to place the user control in any part of the layout.

 

layout

 

Design:

 

Looking first for the clearer solution, it seems that we need a protected field in BasePage.cs to set whether or not the transaction needs authorization, and consequently, if the inherited page should render the authorization user control.

 

Then, we will need to set this property somewhere in MyPage.aspx.cs but before the base class rendering logic is executed. The ASP.NET Page Lifecycle Overview indicates that the best event to set this is Init because this event is “raised after all controls have been initialized and any skin settings have been applied. Use this event to read or initialize control properties.”

 

Finally, it is necessary to place the logic that decides if the user control is added to the collection of controls of the final page, depending on the value of the protected property set before. But this must be done by the Init event on the base page BasePage.cs. To allow the inherited page to place the user control in any place in the layout, the control will be added to the collection of controls of a specific placeholder. For this example it will be named plhAuthControl.

 

Ok, the solution is defined but there is a technical challenge. How do I find the control plhAuthControl? First I have to find the HtmlForm because my final page is using a master page and it adds an extra set of elements to my control hierarchy. In this case, the HtmlForm is in this.Controls[0].Controls[3]. The ContentPlaceHolder which has the controls for the inherited page is in this.Controls[0].Controls[3].Controls[1]. The PlaceHolder I wanted to find is in this ContentPlaceHolder so I was able to do the following for accessing it:

 

this.Controls[0].Controls[3].Controls[1].Controls[9]
this.Controls[0].Controls[3].Controls[1].FindControl("plhAuthControl")

The problem with this approach is that I don’t want the control hierarchy to be fixed. The Master layout and inherited page control containers will change in time, so this base class will no longer be useful and it will have to be modified. One of the ideas that came to my mind was a recursive FindControl implementation so I would do this:

this.FindControlRecursive(this.Master, "plhAuthControl")

…and I get the control immediately, no matter what the control hierarchy is. Before starting to implement the function I found a Rick Strahl’s blog post on exactly this topic, written more than two years ago. Excellent as all the post written by Rick, it had the implementation of the function I was looking for:

public static Control FindControlRecursive(Control Root, string Id)
{
 if (Root.ID == Id)
 return Root;
 foreach (Control Ctl in Root.Controls)
 {
 Control FoundCtl = FindControlRecursive(Ctl, Id);
 if (FoundCtl != null)
 return FoundCtl;
 }
 return null;
}

Simple. Problem solved. But I started reading the comments and I noticed the first one was written by Scott Guthrie (a.k.a. The Gu), first giving a reason why there is not a recursive FindControl function in ASP.NET:

 

“Way back when we debated having a recursive FindControl method. One thing we worried about what the performance impact of people mis-using it -- since doing deep walks of the entire control tree lots of time can really slow things down if you don't know what you are doing.”

 

And then explaining the solution:

 

“Going back to your problem above, you should be able to just declare the control names as protected fields on your base class -- ASP.NET 2.0 wires these up just like V1.1 does (if a page has a base class and it has the controls already declared, then it doesn't re-generate them on any sub-class).”

 

In addition, Scott Allen (a.k.a. OdeToCode) contributed with this:

 

“Try using CodeFileBaseClass in your @ Page directive. This let the ASP.NET code generator find those protected fields in your base class and wire them to the controls in the aspx.”

 

Ok. Thanks to Rick, ScottGu and OdeToCode I finally understood that I don’t need any recursive FindControl function, just place the CodeFileBaseClass attribute in my @Page directive pointing to my base class filename, and placing a protected field on my base class with the same name of the control on the aspx inherited page. Elegant!.

 

Solution:

 

Once all the technical challenges have been analyzed, and according to our initial requirement, this is a reference implementation for our solution:

 

  1. BasePage Class

 

Declare the protected field used for deciding if the user control must be added or not.

 private bool hasAuthorization = false;
 protected bool HasAuthorization
 {
 get { return hasAuthorization; }
 set { hasAuthorization = value; }
 }

Declare a protected field that matches the name of the control on MyPage.aspx so it can be accessed directly from the base class code.

 protected System.Web.UI.WebControls.PlaceHolder plhAuthControl
 {
 get;
 set;
 }

Override the method OnInit to add the logic for adding or not the user control.

 protected override void OnInit(EventArgs e)
 {
 if (HasAuthorization)
 {
 System.Web.UI.UserControl usercontrol = LoadControl("UserControl.ascx") as System.Web.UI.UserControl;
 plhAuthControl.Controls.Add(usercontrol);
 }
 base.OnInit(e);
 }
  1. Inherited Page Code Behind (MyPage.aspx.cs)

 

Make sure the page is inheriting from the BasePage

public partial class MyPage : BasePage

Override the method OnInit for setting the value that indicates if the page must render the user control (in this case it will be rendered):

 protected override void OnInit(EventArgs e)
 {
 HasAuthorization = true;
 base.OnInit(e);
 }
  1. Final Page (MyPage.aspx)

 

Place the CodeFileBaseClass attribute in the @Page directive to specify the base class code file, and make sure the MasterPageFile attribute is set.

<%@ Page CodeFileBaseClass="BasePage" 
 Title="My Page" 
 Language="C#" 
 MasterPageFile="~/View/MasterPage.master" 
 AutoEventWireup="true" 
 CodeFile="MyPage.aspx.cs" 
 Inherits="MyPage" %>

Place a PlaceHolder control named plhAuthControl in the page

<asp:PlaceHolder ID="plhAuthControl" runat="server" />

This sample shows how to use the attribute CodeFileBaseClass for wiring aspx page controls with protected field on the base class automatically. It also shows the technical challenge of combining visual inheritance (master pages) and class inheritance, using C# in this case.