Making a Composite Control with ASP.NET

Aug 27, 11:00 pm

Article Author: Haroon Malik
.NET 3.5 Books

Introduction


When compared with the previous versions of ASP, ASP.NET contains a lot of new features that distinguish it from its predecessors. One of the great new additions, within this respect, is the provision of Web Server Controls that execute on the server, thus resulting in the provision of more rich functionality and more dynamic web application development.

The Web Server Controls provided by ASP.NET can be divided into two major categories:

  • HTML Server Controls derived from System.Web.UI.HtmlControls
  • ASP.NET Server Controls derived from System.Web.UI.WebControls

I will not be going into the details of what these controls are, or what the difference is between the two control types, because this is not within the scope of our discussion. If you would like more details about these controls then please refer to my article on HTML and ASP.NET Server Controls.

Both the above-mentioned categories contain various controls, but what if you face one of the following conditions:

  • You use a collection of controls (either HTML or ASP.NET server controls) to make an interface regularly within your projects and are required to make a single control that you could easily embed within your projects.
  • You wish to provide your control in compiled form, so that the code remains hidden from the developers.

The answer to the first condition is make a User Control, but that doesn’t solve all your problems, because the code of a User Control is not in compiled form and any person can easily view the implementation by simply opening up the file in any editor – the answer to this is Composite Controls.

Composite Controls:

Ok, so how do we define Composite Controls ? Well, Composite Controls are user defined controls that are similar to business logic components, that is, they are compiled, but they differ from them in that they also generate an interface.

A composite control is created by the combination of existing controls and renders a user interface that reuses the functionality of existing controls. A composite control can synthesize properties from the properties of its child controls and handle events raised by its child controls, it can also expose custom properties and events.

Composite Controls Vs User Controls:

User Controls are just partial ASP.NET pages that can be linked with a main ASP.NET page. The code of the User Controls is neither hidden from the users and nor is it in compiled form. Just like normal ASP.NET pages they get loaded with the main ASP.NET page within which they have been linked.

Composite controls are in a compiled form (in the form of a dll file). These controls are derived from the System.Web.UI.Control namespace and are more easily reusable.

System Requirements

The requirements for this article are:

  • Text Editor (Notepad or any other of your choice)

  • Web Browser (any browser will work, because the pages will be executed on the server)

  • A web server IIS (ASP.NET requires IIS 5.0 or later – this means you should be running Windows 2000 or Windows XP or the .NET Server on your system)

  • .NET Framework

  • Microsoft Access (2000/XP)


Defining The Problem

In most web-based projects we are required to login to our accounts or web applications to perform daily tasks, which might include checking emails, managing web contents, updating the sites database and so on. So most web applications have a login form for the users, so that only authenticated users may provide username and password to login to accounts and work, while keeping unwanted and unregistered users away.

We will create a composite login that will be in the form of a compiled assembly and will be available for us to use in our various projects.


Developing a Composite Control

In the following sample we will be creating a composite Login control. The control will consist of six ASP.NET server controls: two Textbox controls, two Label controls and two Button controls. One of the textboxes will be used for taking the username and the other one for the password. After providing the username and password the user clicks the Submit button, which would raise the specified custom event. Similarly if the Reset button is pressed it would also raise a custom event specified for it.

All the existing ASP.NET server controls that combine to form a composite control become the child controls for the composite control.

Before going any further I would suggest you to create a folder by the name of LoginForm within your Inetpubwwwroot directory, and place all your files that we will be creating later in this article, in this folder.

The following is the complete listing of the code for the Login Form composite control component:


Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Drawing
Imports System.Web.UI.WebControls

Namespace WebForms

Public Class LoginForm Inherits Control Implements INamingContainer

Private _ForeColor As Color Private txtUserName As TextBox Private txtPassword As TextBox Public Event Reset As EventHandler Public Event Submit As EventHandler Public Property ForeColor() As Color Get return _ForeColor End Get Set _ForeColor = Value End Set End Property Public Property UserName() As String Get Me.EnsureChildControls() Return txtUserName.Text End Get Set Me.EnsureChildControls() txtUserName.Text = Value End Set End Property Public Property Password() As String Get Me.EnsureChildControls() Return txtPassword.Text End Get Set Me.EnsureChildControls() txtPassword.Text = Value End Set End Property Protected Overrides Sub CreateChildControls() Dim lblUserName As New Label lblUserName.Text = "Username: " lblUserName.ForeColor = _ForeColor Controls.Add(lblUserName) txtUserName = New TextBox() Controls.Add(txtUserName) Controls.Add(New LiteralControl("<BR>")) Dim lblPassword As New Label lblPassword.Text = "Password: " lblPassword.ForeColor = _ForeColor Controls.Add(lblPassword) txtPassword = New TextBox() txtPassword.TextMode = TextBoxMode.Password Controls.Add(txtPassword) Controls.Add(New LiteralControl("<BR>")) Dim btnSubmit As New Button() btnSubmit.Text = "Submit" btnSubmit.CommandName = "Submit" btnSubmit.ForeColor = _ForeColor Controls.Add(btnSubmit) Controls.Add(New LiteralControl("&nbsp;")) Dim btnReset As New Button() btnReset.Text = "Reset" btnReset.CommandName = "Reset" btnReset.ForeColor = _ForeColor Controls.Add(btnReset) End Sub Protected Overrides Function OnBubbleEvent(Source As Object, Sender As EventArgs) As Boolean Dim handled As Boolean = False If TypeOf Sender Is CommandEventArgs Then Dim ce As CommandEventArgs = CType(Sender, CommandEventArgs) If ce.CommandName = "Submit" Then onSubmit(ce) handled = True ElseIf ce.CommandName = "Reset" Then onReset(ce) handled = True End If End If Return Handled End Function Protected Overridable Sub onSubmit(Sender As EventArgs) RaiseEvent Submit(Me, Sender) End Sub Protected Overridable Sub onReset(Sender As EventArgs) RaiseEvent Reset(Me, Sender) End Sub

End Class

End Namespace

Save this file as LoginForm.vb.


How It Works

First of all, the required namespaces for the development our control have been imported:


Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Drawing

After that we will create a namespace for our control in which the class for the Login control is going to be created. I have named the namespace as WebForms:


Namespace WebForms

Next we create a class named as LoginForm and inherit this class from the Control class. This class implements the INamingContainer interface:


Public Class LoginForm
  Inherits Control
  Implements INamingContainer

Why have we done this? The reason is that our composite control is required to do the following:


  • To override the protected CreateChildControls method inherited from Control to create instances of the child controls and add them to its Controls collection.

  • To implement the System.Web.UI.INamingContainer interface. INamingContainer is a marker interface that has no methods. When a control implements INamingContainer, the ASP.NET page framework creates a new naming scope under that control, thus ensuring that the child controls have unique names in the hierarchical tree of controls. This is especially important when a composite control exposes template properties, provides data binding, or needs to route events to its child controls.


The next lines list the data members that would be used within our class:


     Private _ForeColor As Color
     Private txtUserName As TextBox
     Private txtPassword As TextBox

Public Event Reset As EventHandler Public Event Submit As EventHandler

Note that the data type Color is available when the System.Drawing namespace has been imported.

The first three of the data member variables have been declared Private, which means that they will not be accessible outside of the class. The other two data member variables created are actually of type EventHandler. We will be using them for handling the events that are going to be raised by the Button controls on our Login control.

Next are listed the public properties for the control that would determine the functionality or the look of the interface for our control:


     Public Property ForeColor() As Color
     Get
          return _ForeColor
     End Get

Set _ForeColor = Value End Set End Property

This property determines the color of the text appearing on our Login control. Setting this property of the control will set the specified color on the text appearing in the Label and the Button controls.


     Public Property UserName() As String
     Get
          Me.EnsureChildControls()
          Return txtUserName.Text
     End Get

Set Me.EnsureChildControls() txtUserName.Text = Value End Set End Property

This property will be used to set or get the username value that has been specified within the Textbox control for obtaining the username. The Me.EnsureChildControls() method within the code of the property has been placed to make sure that the control related to property has been created before setting or getting the value of the property.


     Public Property Password() As String
     Get
          Me.EnsureChildControls()
          Return txtPassword.Text
     End Get

Set Me.EnsureChildControls() txtPassword.Text = Value End Set End Property

This property is similar to the last property that we saw, it is used to set or get the password value that has been specified within the Textbox control for obtaining the password.

Next within the code we have a method by the name of CreateChildControls(), as mentioned earlier, this method belongs to Control and must be overridden by the composite control to create instances of the child controls and add them to its Controls collection.


     Protected Overrides Sub CreateChildControls()

Dim lblUserName As New Label lblUserName.Text = "Username: " lblUserName.ForeColor = _ForeColor Controls.Add(lblUserName)

These four lines of code have been written to create a new Label control and set its properties. The Text property of the control has been assigned the string that will appear within the Label control, similarly the ForeColor property of the Label control has been assigned the value of the data member variable _ForeColor of the class that will apply the color specified in the variable, to the text that appears within the Label control. The last line contains the Add() method. This method is used to add a child control within the composite control that is being created. You will see that all the controls that are going to be mentioned here or are going to be included within our Login control will have the Add() method called.


          txtUserName = New TextBox()
          Controls.Add(txtUserName)

These two lines simply create a new Textbox control as a child control of our Login control. The last line contains the Add() method that will include the Textbox control named as txtUserName within our composite control.


          Controls.Add(New LiteralControl("<BR>"))

This line simply adds a Break Rule, so that the next listed controls can appear on the new line instead of appearing along the Label and Textbox control that we have created within the above lines.


          Dim lblPassword As New Label
          lblPassword.Text = "Password: "
          lblPassword.ForeColor = _ForeColor
          Controls.Add(lblPassword)

txtPassword = New TextBox() txtPassword.TextMode = TextBoxMode.Password Controls.Add(txtPassword) Controls.Add(New LiteralControl("<BR>"))

As you can see, there was nothing new within the above lines, everything here has been explained earlier, except that these are to create a Label control with the text Password appearing within it and a Textbox control for obtaining the password from the user. The TextMode property here to specify that the text being written in the control should be replaced by the password characters, to ensure secrecy of the password. After that simply another Break Rule has been added to display a line break.


          Dim btnSubmit As New Button()
          btnSubmit.Text = "Submit"
          btnSubmit.CommandName = "Submit"
          btnSubmit.ForeColor = _ForeColor
          Controls.Add(btnSubmit)

This portion of the code is similar to the above portions, but the difference here is that this time we are creating a Button control and are setting the properties for its appearance. The Text property, as usual, displays the specified string on the Button control, and the ForeColor property here also has been specified the data member _ForeColor control, so that the specified color is applied on the text appearing on the Button control. The CommandName property is used to assign a unique name upon the basis of which a command control can be differentiated from another one. As our control will be having two Button controls, one for Submit and the other for Reset, we will be distinguishing the events that are raised from both these controls on the basis of the CommandName property of these controls. You will be seeing this implemented in the code, soon.


          Controls.Add(New LiteralControl("&nbsp;"))

Instead of adding a Break Rule here I am now adding a simple space so that both the Button controls appear side-by-side.


          Dim btnReset As New Button()
          btnReset.Text = "Reset"
          btnReset.CommandName = "Reset"
          btnReset.ForeColor = _ForeColor
          Controls.Add(btnReset)

End Sub

The method ends here, but before finishing the method I have implemented a few lines of code for the creation of another Button control – the Reset button. Note that the CommandName property for this Button control is different from the Submit button control.

The following methods that I will be explaining now, are mainly for the purpose of handling the events that are going to be raised by the Button controls that have been used in building up our Login control. For the purpose of handling the events raised by our child controls, here we will be using a technique called Event Bubbling. Let’s first of all see that what Event Bubbling is:


Event Bubbling

The ASP.NET framework provides the event bubbling technique, to expose the events raised by the child controls as top-level events. As you know, our Login control will be having two Button controls within it. Upon the click event of these two buttons the user would like to specify some code that should execute as soon as the event is raised. Since we cannot allow the users of our control to directly access the events of the child controls that compose our Login control, we will make the events of the child controls appear as if they were being raised by the Login control itself. Event bubbling is also used by the data-bound controls in ASP.NET like Repeater, DataList, and DataGrid to expose command events raised by child controls (within item templates) as top-level events.

A control can participate in event bubbling through two methods that it inherits from the base class System.Web.UI.Control. These methods are OnBubbleEvent and RaiseBubbleEvent. We will be using the OnBubbleEvent for our event handling. So here is the code for the method that we override for usage within our own class.


     Protected Overrides Function OnBubbleEvent(Source As Object, Sender As EventArgs) As Boolean

We create a function over here and this function will return us a Boolean value, true will be obtained if the event is handled and false if it is not.


          Dim handled As Boolean = False

Here is the declaration of the variable that will be returned by the function. By default its value has been set as False.


          If TypeOf Sender Is CommandEventArgs Then

Dim ce As CommandEventArgs = CType(Sender, CommandEventArgs)

The implementation of the If condition here is to determine and make sure that the event been sent is from a Command Button control and not from some other control within the collection. If the condition is true and the event has been sent from a Button control then the events arguments are going to be passed to ce. The CType keyword converts the Sender to appropriate CommandEventArgs type and assigns it to ce.


               If ce.CommandName = "Submit" Then

onSubmit(ce) handled = True ElseIf ce.CommandName = "Reset" Then onReset(ce) handled = True End If End If

Next, we have determined the CommandName property to find out about the Button control, which raised the event. The onSubmit() and onReset() methods will be called depending upon the value of the CommandName property, and the value of the variable handled is going to be set to True.


          Return handled

End Function

Afterwards we simply return the value of the Boolean variable and end our functions body. Next we have to define the onSubmit() and the onReset() methods. These methods will be used solely for the purpose of raising the events.


     Protected Overridable Sub onSubmit(Sender As EventArgs)
          RaiseEvent Submit(Me, Sender)
     End Sub

Protected Overridable Sub onReset(Sender As EventArgs) RaiseEvent Reset(Me, Sender) End Sub

The Submit and Reset events used over here are those that we declared at the start of the class as public events. These events are publicly available, thus allowing the users of the control to write the response of the events themselves.

Now we will simply wrap everything up by ending the scope of our class and the namespace.


End Class

End Namespace

Remember to save this file as LoginForm.vb.

So that’s it for the coding, now what is required is to compile this code and create a compiled dll file, which we will be including within our projects for using this control.

Open up Notepad or any editor of choice and write down the following.


set indir=C:inetpubwwwrootLoginFormLoginForm.vb
set outdir=C:inetpubwwwrootbinLoginForm.dll
set assemblies=System.dll,System.Data.dll,System.Web.dll,System.Drawing.dll

vbc /t:library /out:outdir indir /r:assemblies

The first line is:


set indir=C:inetpubwwwrootLoginFormLoginForm.vb

This will set the path to the file that would be acting as the source file, this path will be saved in indir. In a similar manner the next line sets the path where the compiled file, which is going to be a dll, will be placed and the path is being saved in outdir. Remember to create a bin directory within the LoginForm directory where we are saving all our files related to this article.


set assemblies=System.dll,System.Data.dll,System.Web.dll,System.Drawing.dll

This line indicates the required assemblies of the .NET framework by our source file and assigns it to assemblies.


vbc /t:library /out:outdir indir /r:assemblies

In this last line we call the compiler and provide it with the required parameters, for the purpose of compilation. Save this file as MakeControl.bat, in the LoginForm directory.

For more information about the compiler options and command line compilation, please see http://aspalliance.com/hrmalik/articles/command_line_compilation.aspx.

Running MakeControl.bat produces the following list of processes:

If this process wouldn’t have had been successful, you would have been displayed a list of errors on the prompt, demanding that you remove those errors first and then go for the compilation again.

Now if you open up your bin directory and check it, you would find a file by the name of LoginForm.dll in there. This is the compiled form of our control that we will be using in our projects. To test this file we will create a simple page and place this control within it, but before that we need to create a database, in which the list of our users is going to be created so that only listed users may login.

Database Design

I will be creating a simple database in Microsoft Access and use this for the demonstration purposes. Open up Access and create a new table with three fields in it.















Field NameData Type
IDAutoNumber
UserNameText
PasswordText

Name the table as Users and name the database as Central.


UI Design

Open up notepad or any other editor of your choice and write the following code:


<%@ Register TagPrefix="Wrox" Namespace="WebForms" Assembly="LoginForm" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.Oledb" %>

<Script Language="VB" Runat="Server">

Private Sub SubmitHandler(Source As Object, Sender As EventArgs) Dim objConnection As OledbConnection Dim objDataReader As OledbDataReader Dim strConnect As String = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source = " & Server.MapPath("Central.mdb") & ";" Dim strQuery As String = "SELECT * FROM Users WHERE UserName = ‘" & frmPass.UserName & "’ AND Password= ‘" & frmPass.Password & "’;" objConnection = New OledbConnection(strConnect) objConnection.Open() Dim objCommand As New Oledbcommand(strQuery, objConnection) objDataReader = objCommand.ExecuteReader if objDataReader.Read() = True Then objConnection.Close() objDataReader.Close() Response.Redirect ("Welcome.htm") else Response.Write("Invalid UserName or Password.") end if End Sub Private Sub ResetHandler(Source As Object, Sender As EventArgs) frmPass.UserName = " " frmPass.Password = " " End Sub

</Script>

<html>
<body>

<form runat="server"> <table width="60%" border="2" cellspacing="0"
cellpadding="0" align="center" bordercolor="#000000"
bgcolor="#FFFFFF"> <tr> <td align="Center"> <Wrox:LoginForm Id="frmPass"
ForeColor="Red" onSubmit="SubmitHandler"
onReset="ResetHandler" runat="server" /> </td> </tr> </table>
</form>

</body>
</html>

Save this page as default.aspx.


How It Works

Let’s start from the top:


<%@ Register TagPrefix="Wrox" Namespace="WebForms" Assembly="LoginForm" %>

As with User Controls, we are also required to provide information related to the Composite Control that we have to use within our page. The attributes required by this tag are quite self-explanatory; the first one TagPrefix requires the name by which we would be calling our control in the page, for example, <Wrox:LoginForm runat="server">.

The second attribute requires the namespace to which the control belongs and the last one requires the name of the assembly file, which is the compiled dll file that we have for our control.


<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.Oledb" %>

Since we will be using the MS Access database to verify our users, we need to import these namespaces in order to use the classes related to OLEDB database handling.

Within the script tags that would execute on the server-side, we will be placing the events for our controls. The first event related to the Submit button of our control is as given below.


Private Sub SubmitHandler(Source As Object, Sender As EventArgs)

Dim objConnection As OledbConnection Dim objDataReader As OledbDataReader Dim strConnect As String = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source = " & Server.MapPath("Central.mdb") & ";" Dim strQuery As String = "SELECT * FROM Users WHERE UserName = ‘" & frmPass.UserName & "’ AND Password= ‘" & frmPass.Password & "’;" objConnection = New OledbConnection(strConnect) objConnection.Open() Dim objCommand As New Oledbcommand(strQuery, objConnection) objDataReader = objCommand.ExecuteReader if objDataReader.Read() = True Then objConnection.Close() objDataReader.Close() Response.Redirect ("Welcome.htm") else Response.Write("Invalid UserName or Password.") objConnection.Close() objDataReader.Close() end if End Sub

This procedure would be fired on the click event of the Submit button of our control. After being clicked it would create connectivity with the database and would verify the provided UserName and Password with those present in the database. If found, the user would be redirected to a Welcome.htm page, otherwise he would be displayed a message saying the login details were invalid.


     Dim objConnection As OledbConnection
     Dim objDataReader As OledbDataReader
     Dim strConnect As String = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source = 
" & Server.MapPath("Central.mdb") & ";"

We first create the Connection and Reader objects and then a string, strConnect, which is assigned the connection string for our database.


     Dim strQuery As String = "SELECT * FROM Users 
WHERE UserName = ‘" & frmPass.UserName & "’ AND Password= ‘"
& frmPass.Password & "’;"

Another string strQuery here contains the SELECT query on the basis of which the provided username and password are going to be matched with the existing ones. Here you would be seeing frmPass.UserName and frmPass.Password as something new. frmPass is the name of our control and the UserName and Password are the properties of our Login control that retrieve the values of the child Textbox controls.


     objConnection = New OledbConnection(strConnect)
     objConnection.Open()

Dim objCommand As New Oledbcommand(strQuery, objConnection)

objDataReader = objCommand.ExecuteReader

The connection object is created and opened and a command object is also being created. Afterwards the ExecuteReader method is called to place all the results into the data reader object.


     if objDataReader.Read() = True Then
          objConnection.Close()
          objDataReader.Close()
          Response.Redirect ("Welcome.htm")
     else
          Response.Write("Invalid UserName or Password.")
          objConnection.Close()
          objDataReader.Close()

end if End Sub

Based on the results, if the data reader object contained something the condition would be true and the relevant lines of codes would be executed, redirecting the user to the welcome page. Otherwise the user would be brought back to the same page and a message would be displayed to him indicating that the username or the password provided is not correct and must login again.


Private Sub ResetHandler(Source As Object, Sender As EventArgs)

frmPass.UserName = " " frmPass.Password = " " End Sub

This procedure is to handle the event raised by the Reset button of our control. The method simply clears the text within the child Textbox controls of our Login control.

Within the HTML portion of the code, most of it should be familiar to you, so I will just explain this line:


<Wrox:LoginForm Id="frmPass" 
ForeColor="Red" onSubmit="SubmitHandler"
onReset="ResetHandler" runat="server" />

This is similar to how we create ASP.NET server controls, such as:


<ASP:Button Id="btnOk" Text="Submit" Runat="Server"/>

This tag is responsible for the creation of our control on the page. As you can see that it has been provided with an Id, the ForeColor property has also been specified because of which the text of the controls is going to appear in red, which we will see in a few moments. The event handlers are also specified within the tag the onSubmit event has been assigned the SubmitHandler procedure and the onReset event has been assigned the ResetHandler procedure.

Please note that you can also set the UserName and Password properties for the control at this stage, but it would be useless and unsafe to assign these properties by default.

Now create another page by the name of Welcome.htm. Open up notepad or any editor of choice and write the following lines within it.


<html>
     <head>
          <title>Welcome</title>
     </head>
     <body>
          Welcome to your Login!
     </body>
</html>

If the user has provided the right username and password he would be redirected towards this page.

This isn’t over yet. We have one last thing to do before testing our application and that is to create a Web.Config file and add the reference to our Login control’s assembly in there. Open up notepad and simply write the following lines:


<configuration>
  <system.web>
    <compilation>
      <assemblies>
     <add assembly="LoginForm"/>
      </assemblies>
    </compilation>
  </system.web>
</configuration>

Save this file as Web.config, and be prepared to see your control in action. Open up Internet Explore or any other compatible browser and add the URL to your page. If you have followed according to my instructions your URL will be: http://localhost/LoginForm/default.aspx

Here is what the page should like:

When you provide the correct username and password, you would be redirected towards the Welcome.htm page. Please make sure first, that the username and password you provide is also given in the Users table within the database.

If the wrong username or password has been provided, then you will see the control with the following message:

The Sample Application

The following files are included with the sample application being provided.

  • Central.mdb (Microsoft Access database file)

  • MakeControl.bat

  • LoginForm.vb

  • LoginForm.dll

  • Default.aspx

  • Welcome.htm


The sample application being provided with this article consists of all the required files. You only need to unzip the support material zip file to your inetpubwwwroot directory.


Further Work

As far as further work is concerned, each user can find and add his requirements to this control and use it as he requires. In a similar manner we may also make controls of other forms that are of common use within web-based applications, like the User Information form, that is often used for signups etc. Similarly commonly used interfaces can be converted into controls easily as ASP.NET allows easy development of the controls within its own framework.


Conclusion

This article introduced composite controls, stated the difference between user controls and the composite controls, introduced Event Bubbling, and of course, we developed our own composite control to see how composite controls can be developed and how the event bubbling concept for handling events within it can be implemented.

Founders at Work

Commenting is closed for this article.