Here’s something I’ve lamented many times in my ASP.NET travails: The inability to visually inherit anything in ASP.NET. Well, not anything really. You can inherit things easily enough, but only if you never want to touch it again. But try declaring an ASP.NET form with a bunch of controls on it, then subclass it and try to visually edit it. No fun. The other way around is no better - define a base UserControl class (maybe even non-visually) and try to use it as a 'controller' for a subclassed ASCX control - if you're trying to access the subclassed control properties you'll find that VS.NET makes this nearly impossible (although ASP.NET natively supports this). This entry talks about the second scenario.

 

I find this is a common scenario in many of my applications especially for those special framework components. My current scenario is that I built a generic control that has a fairly complex UI (a bunch of controls that get enabled disabled based on the state of the page and the control properties). The base control will be subclassed by a new user control probably in every application and visually rearranged. I really want all of my code to sit in the base class, including the UI code that talks to the contained controls.

 

The issue at hand is this: ASP.NET creates properties for any control that you create on a form or a user control and adds this control to the page’s code behind file as a property definition. So if you drop a label on a form or user control you’ll get:

 

      protected System.Web.UI.WebControls.Label lblTitle;

 

in your CodeBehind file. In most cases this is great. You can reference the property in your Code Behind file which makes it easy to manipulate the contained objects on your forms and user controls. But this model gets to be problematic if you start with a base page that the CodeBehind page inherits from.

 

If I want to have my page or user control inherit from a base class I can easily create a base control like this:

 

      public class mmBaseUserLoginControl : UserControl

 

And then have the CodeBehind class inherit from that with:

 

          public class mmLoginControl : mmBaseUserLoginControl

 

All the common functionality sits in the base control and mmLoginControl simply calls into the base classes methods to perform most tasks. The problem is that it’s very difficult to delegate the UI tasks back to this base form which is what I am really after even if I declare the label property in the base class (manually).


As I mentioned the above mmLoginControl form is fairly complicated. There are about 20 child controls in this control to handle a number of aspects for logging in, changing passwords, password retrieval etc. All the business logic (that talks to a business object) sits in the mmBaseUserLoginControl. But there’s still a ton of UI logic that deals with activating the UI properly etc. that is duplicated everytime the UserControl is added to a new project.

 

Why oh why not just put all that code in the base class? I would if VS.NET wouldn’t screw with my property definitions. Here’s the problem:

 

VS will create a property definition for any control in a User Control or form in the immediate code behind file.

 

protected System.Web.UI.WebControls.Label lblTitle;

 

In order for my base class to talk to this label I would need a property inside of the mmBaseUserLoginControl.  Now if I remove control that VS added to my inherited form and stick it into the base form – lo and behold the code works just fine. Wo hoo. ASP.NET understands what I want to do.

 

Unfortunately next time you open the control and save it inside of VS, VS will proceed to immediately put the control back into the CodeBehind file, which then breaks the inheritance structure. When VS places the control in the codebehind it basically overrides the base field which means that the base field never gets updated. If you access the base field from the base control the control will always be null. Awk. Remove the control from the codebehind and all works well.

 

Now, if this was an application that I control all the way through I would just hack this with a comment like this:

 

            // *** IMPORTANT:

            //     If properties of form controls show up here from the visual editor

            //     delete them. These values will override the default behavior from

            //     from the default ItemList page that this page is inherited from.

 

and hopefully remember to remove the controls after any edits. Not pretty but easy enough to remember – delete the properties whenever they show up and off you go. However, this is still scary because this will cause a runtime error (control reference is null - as soon as the base talks to the control: Bang!).

 

So another way to do this is to go ahead and let VS do its thing and end up with double declarations in the CodeBehind as well as the base class. The CodeBehind file gets the actual control assignments when the page/control is activated. The base does not because as far as the Page/Control is concerned the CodeBehind properties hide the base control properties of the same name. So what we need to do is delegate the assigned control references back to the Base class.

 

In the base class you might have a method like this:

 

private void AssignControls()

{

      this.txtUsername = this.FindControl("txtUserName");

      this.txtPassword = this.FindControl("txtPassword");

}

 

You can call this method from the base control/form’s OnLoad() method to assign the references.

 

If you don’t mind a little more overhead and you have a consistent naming scheme for your controls you can automate this process a little more by using Reflection:

 

/// <summary>
///
This routine is used to 'copy' the references of the inherited control into
/// the above base controls which makes it possible to reference the controls
/// from the base class.
/// </summary>
///
<remarks>
///
This code is not terribly efficient as it uses reflection to loop
/// through all of the protected properties and assign each a new value.
/// If you want better performance you can directly add a method that
/// performs these assignments using FindControl directly.
/// </remarks>
///
<param name="Prefixes"></param>
private void AssignLocalControls(string Prefixes)
{
      if (Prefixes == null || Prefixes == ""
            Prefixes = "txt,chk,btn,cmd,rad,tbl,lbl";

  

      // *** Must create a local copy of the object for relfection to

      // *** work against this level instead of the inhertied page

      // *** which will contain the same 'overridden' properties.

      // *** without this reflection always finds the overridden members.

      mmBaseUserLoginControl TLocalCtl = new mmBaseUserLoginControl();

     

      Prefixes = "," + Prefixes + ",";

      FieldInfo[] fi = TLocalCtl.GetType().GetFields( BindingFlags.NonPublic |

                                     BindingFlags.Instance | BindingFlags.DeclaredOnly );

 

      foreach(FieldInfo field in fi)

      {

            string FieldName = field.Name;

           

            string prefix = FieldName.Substring(0,3);

            if (  Prefixes.IndexOf( "," + prefix + ",") < 0)

                  continue;

 

            Control FoundCtl = this.FindControl(FieldName);

 

            // *** Read the Field from

            FieldInfo LocalField = TLocalCtl.GetType().GetField(FieldName,BindingFlags.NonPublic | BindingFlags.Instance);

            Control LocalCtl = LocalField.GetValue(this) as Control;

 

            LocalField.SetValue(this,FoundCtl);

      }

 

}

 

 

This code in essence loops through all the fields of the base class and assigns the result from FindControl to each member that matches a specific signature – in this case anything that starts with a specific three letter prefix that I always use for my controls. This effectively copies the references of all controls down to the base form.

 

There’s a real gotcha in this code by the way. Notice that I explicitly had to declare my type that I’m reflecting over at the top of the code:

 

mmBaseUserLoginControl TLocalCtl = new mmBaseUserLoginControl();

 

I started using the this operator first, and that didn’t work because this was always pointing at the full inherited control. Even casting this down to the base type didn’t work. However, explicitly using the type itself for Reflection to use did the trick and allowed me to access the ‘local’ base properties I need to set.

 

This approach basically lets you move all code out of a Form or User Control into the base class because now the base class can access the controls directly. In order for this to work though you still have to copy any controls that are on the form and that you want to access in the base control/form. Note that you really should move only what you need. Controls you don’t reference don’t need to be copied. The compiler will tell you when you missed something.

 

This is still pretty messy. Maybe there’s an easier way, but I haven’t seen it.

 

FWIW, Whidbey will not make this any easier. In fact, in Whidbey without CodeBehind you will never see the control declarations defined in the base form as Whidbey declares these controls at runtime JIT compile time. The approach above will still work, but you’ll have to create each control declaration explicitly rather than just cutting and pasting once.

 

On the other hand for a more serious type of control I suppose it would make sense to ditch the User Control approach altogether and just build a full fledged Server Control which provides more options. Unfortunately you loose the ability to easily modify the layout higher up in the hierarchy unless you build a fairly sophisticated UI handler into the control. Trade offs... but it sure would be nice if there was an easier solution to building some mechanism of encapsulation into ASP.NET - or rather the ASP.NET designers. If the designers would understand that there is a control defined in the base class (not the code behind class) and would leave alone that control, this whole thing would actually work without any kludges. Of course in Whidbey this becomes even more problematic as controls get injected at runtime...

 

This is something that will have to be addressed sooner or later in the product...