Thomas Alva Edison's light bulb VBInfoZine Home
   An ordinary VB developer shares his own successes and failures
   FREE to registered subscribers.  

Inheriting An ASP.NET Server Control

How to easily customize an ASP.NET server control by deriving from it.

After developing several ASP.NET WebForms applications, I have to say that ASP.NET is a great framework. Despite the fact that my knowledge about the ASP.NET architecture is near to nothing, I was still able to develop something useful (I hope :-)).

For the most part, my ASP.NET applications are simple, ad-hoc front-ends to some back-end functionality (see http://www.nrsr.sk/bin/net/nrozprava/ as an example). I didn't need to use any sophisticated custom controls; just a UserControl here and there in order to encapsulate some repeating functionality within a single application.

Sometimes, however, I needed to extend a built-in WebControl by deriving from it. You also can use this approach when one of the ASP.NET-provided controls "almost" meets your needs, but lacks some required bit of functionality.

Let's illustrate the concept with an example:

Your ASP.NET application should display a form with several buttons on it. When clicked, some of the buttons should execute the associated (server-side) event handler code directly. Some of the buttons, however, have to display a confirmation prompt on the client browser, allowing the user to cancel the initiated execution of the command.

Because the buttons that should display the client-side confirmation are...well, buttons, it is natural (IMHO) to derive a new class from the existing System.Web.UI.WebControls.Button and add the necessary functionality there. Enter the PromptButton class:

Namespace LaMarvin.Samples.Web.DerivingBuiltinControls

Public Class PromptButton
  Inherits System.Web.UI.WebControls.Button
 
  ' Our confirmation message or Nothing if no
  ' confirmation prompt should be displayed on the client.
  Private _ConfirmMessage As String = String.Empty
 
  Public Overridable Property ConfirmMessage() As String
    Get
      Return _ConfirmMessage
    End Get
    Set(ByVal Value As String)
      _ConfirmMessage = Value
    End Set
  End Property

  Protected Overrides Sub AddAttributesToRender( _
    ByVal writer As System.Web.UI.HtmlTextWriter)
    If _ConfirmMessage.Length > 0 Then
      writer.AddAttribute("onclick", _
        "return confirm('" + _ConfirmMessage + "');")
    End If
    MyBase.AddAttributesToRender(writer)
  End Sub

End Class

End Namespace
First, we've declared the _ConfirmMessage member variable and the public ConfirmMessage accessor property.

Second, we've overridden the AddAttributesToRender method adding the onclick client-side script handler.

In order to use the PromptButton class in an .aspx page, the project containing the class has to be rebuilt. This is because we have to register the control's assembly by using a variant of the @Register directive that includes the Assembly attribute:

<%@ Register TagPrefix = "dc"
  Namespace = "LaMarvin.Samples.Web.DerivingBuiltinControls"
  Assembly = "DerivingBuiltinControls" %>
The TagPrefix "dc" is an arbitrary string that is an alias for the associated namespace (as declared by the Namespace attribute).

The Assembly attribute is the assembly DLL file name without the extension (a fully qualified assembly reference isn't necessary in this case).

The following fragment illustrates how the PrompButton class is declared within an .aspx page:

<dc:PromptButton id="WithPrompt" runat="server"
  Text="With Prompt" ConfirmMessage="Are you sure?" />
It is important to note that the string after the tag prefix gets combined with the associated namespace to form the fully qualified type name - LaMarvin.Samples.Web.DerivingBuiltinControls.PromptButton in this case.

That's all if you're adding your derived controls through the HTML view of the VS.NET WebForms designer.

You can also add your derived control to the WebForms designer toolbox so you can create the control's instances using drag & drop from the toolbox. Just right-click the toolbox, choose 'Add/Remove Items...' and browse for the assembly .dll file (it's in the project's bin subdirectory).

In order to better support the design-time experience, you might want to customize the initial HTML code that is generated by the designer when you place a new instance of your control onto a form.

You can do this by applying the System.Web.UI.ToolboxDataAttribute attribute to your derived control class:

<ToolboxData("<{0}:PromptButton runat=""server"" ConfirmMessage=""Hello!"" />")> _
Public Class PromptButton
  Inherits System.Web.UI.WebControls.Button
Note that you must always include the string "{0}", which gets replaced be the actual tag prefix used within the page. Also you have to include the runat = "server" attribute, otherwise the designer won't be able to instantiate your control.

Beware, however, that when you change the ToolboxDataAttribute for a control that you've already put onto the toolbox, you'll have to remove it and re-add it again (recompile doesn't suffice). I don't know the reason for this. It seems that the designer is caching the attribute's value somewhere when the control is added to the toolbox.

Despite this small annoyance, deriving from an ASP.NET server control might be useful technique if you need functionality that "is-a" extension of an existing server control.


Note to cautious readers ...

There are two quite serious problems with the above-described PromptButton implementation.

Take a look at the code once again. Can you spot the problems?

Here are some hints...

  1. PromptButton.ConfirmMessage = Nothing
  2. PromptButton.ConfirmMessage = "What's up?"
OK, the first one was easy. When you assign Nothing to the ConfirmMessage property (in the Page_Load event, for example), System.NullReferenceException will be thrown in the PromptButton.AddAttributesToRender method.

The second one, however, is more dangerous, because it doesn't manifest so "loudly". That is, when the ConfirmMessage string contains an apostrophe, the generated client-side script will be invalid and no confirmation prompt will be displayed on the client at all!

Fortunately, the cure is easy - here is a more robust implementation of the PromptButton class (the additions are marked with bold text):

Imports System.ComponentModel
Imports System.Web.UI.WebControls

<ToolboxData("<{0}:PromptButton runat=""server"" ConfirmMessage=""Hello!"" />")> _
Public Class PromptButton
  Inherits Button

  Private _ConfirmMessage As String = String.Empty

  ' Our confirmation message or String.Empty if no
  ' confirmation prompt should be displayed on the client.
  Public Overridable Property ConfirmMessage() As String
    Get
      Return _ConfirmMessage
    End Get
    Set(ByVal Value As String)
      _ConfirmMessage = Value
      ' Test for Nothing on this one place, instead of
      ' here and there throughout the implementation.
      If _ConfirmMessage Is Nothing Then
        _ConfirmMessage = String.Empty
      End If
    End Set
  End Property


  Protected Overrides Sub AddAttributesToRender( _
    ByVal writer As System.Web.UI.HtmlTextWriter)

    If _ConfirmMessage.Length > 0 Then
      ' Change embedded apostrophes to corresponding 
      ' escape sequences.
      Dim EscapedMessage As String = Replace(_ConfirmMessage, "'", "\'")
      writer.AddAttribute("onclick", _
        "return confirm('" & EscapedMessage & "');")
    End If
    MyBase.AddAttributesToRender(writer)
  End Sub

End Class
Would you say that a few simple lines of the PromptButton code could break the whole application?

© Palo Mraz, Tuesday, October 07, 2003

 ©2003-2007 Palo Mraz. All Rights Reserved.   See my 'new browser window' policy