Object-oriented ASP.NET Programming

Applying Object-oriented Principles to Web Development with ASP.NET

asp:feature

LANGUAGES: VB | C#

TECHNOLOGIES: Object-oriented Programming | Inheritance

 

Object-oriented ASP.NET Programming

Applying Object-oriented Principles to Web Development with ASP.NET

 

By Markus Egger

 

Web development traditionally has been somewhat unstructured. Most development has been done in a very spaghetti-code-like fashion. To make matters worse, code had to be mixed in with HTML. It was procedural at best. Server-side includes were the state of the art for reusability. A few very bright people managed to create very sophisticated Web applications this way. But, overall, it was a very difficult and expensive way to program the Web. With ASP.NET, Microsoft introduces the well-proven object-oriented methodology to Web development. Advantages seem obvious, but you have to take a much closer look to understand the real power of this concept.

 

To truly understand the power of object-oriented development, you need to revisit conventional ASP-style Web development without objects. Consider a simple button that sets the value of a text box on a Web page. This seemingly simple requirement isn t really so trivial on a Web page. First of all, you have to create the appropriate HTML that creates those two controls:

 

 

This will render the appropriate HTML output, but it isn t functional. You need to add some additional infrastructure, such as a

tag, so the HTML page knows what to do whenever someone clicks the button. And there aren t many options at this point. Either you can write a client-side script (which isn t the subject of this article), or you can post the current information to a page of your choice. This would communicate the two form variables (both the text box and the button are really only variables) back to the server. There, you can have server-side code to interpret what came along (which isn t all that trivial, either) and create a new HTML string that represents the appropriate output. Here s the complete code for the first page:

 

   

   

 

Naturally (well, naturally for Web developers, but very weird for other developers), the button s code (the logic that belongs to the button that was clicked) sits in that new page. As you ll see, that s a problem.

 

Again, this seemingly simple scenario isn t so trivial in the real world, where most serious applications have many buttons and many paths of action. The number of possible new output scenarios grows exponentially, and, therefore, most Web pages have very few paths for the user to take. Imagine an online shopping cart, for instance. Once a user has finished shopping and is ready to check out, the site usually asks him or her for a billing address, a delivery address, a credit-card number, and so forth. Very often, this information is retrieved in multiple steps. Sometimes, the Web store already knows about the shopper but still displays the information for verification. Whether that makes business sense or not is debatable. The reality is that even if the store knows certain information, it s simpler to re-display it to the user than to skip it because re-displaying it keeps the number of paths through the scenario at a minimum. In Web applications, it s very difficult to build complex wizards that have a large number of steps and that may skip many of them, or may show them in a different order depending on information entered at previous steps.

 

One of the reasons for these scenarios to be so complicated is the poorly located code that goes with the object that causes actions. Basically, it means you have one specific page to which you navigate as the next step, or you have multiple pages to which to navigate, but each of them needs to have the code for the button.

 

Neither way is very attractive. In the first solution, your single page to which to navigate would need to take into account all the possibilities that may occur next. If you had a wizard that had 20 possible steps, each page would need to implement all those options. And, if one of those steps changed, you d have to change it in all the pages. And it gets worse: What if you wanted to add a few pages? Ouch!

 

The second option is to navigate to different next steps, which would make it much easier to build a flexible 20-step wizard. However, because you wouldn t be sure where to navigate to, each step would have to have logic to handle whatever happened in the previous step.

 

So you see, it s a tossup. Either you have the duplicate effort (or 20-fold effort) for the interface, or you have the same effort for the code that goes along with it.

 

Objects to the Rescue

A solution to this dilemma is to put the code where it belongs: with the button. In that scenario, whenever someone clicks the button, the code that s attached to the button fires, and then you can decide where to navigate to. This concept is known as encapsulation. It s one of the key concepts for all object-oriented and object-based systems, such as Visual Studio .NET and Visual Basic 6, respectively.

 

From a programmer s point of view, there are various ways to link click code to a button. The easiest is event delegation, in which case you add a standard button to a Web Form using the appropriate ASP.NET tags in an aspx file:

 

 

This creates a new button on the page. However, this time, you are not just dealing with strings. Under the hood is code that represents this button. If you use Visual Studio .NET (and for this article, I assume you are), you can look at the source code by right-clicking on the page and selecting View Code. There, you ll see a page object that contains a button object (this is a Visual Basic .NET example):

 

Public Class WebForm1

    Inherits System.Web.UI.Page

    Protected WithEvents Button1 As _

     System.Web.UI.WebControls.Button

End Class

 

Note that I stripped out some code that wasn t significant for this particular example.

 

As I mentioned before, this button is more than just an HTML string that represents the button. It is an object with properties, fields, methods, and events. All these things combine to make up the button. In object-terms, all those things are encapsulated in the button. Most of them you can access from the outside as members of the button, but some of them might be private and can be used only by the button itself. As a whole, each button object is self-contained and doesn t require any code in any other places to function. However, standard buttons aren t very useful by themselves because they don t do anything when they are clicked and don t have a useful caption. You can customize these things by setting properties and writing event code. To write code for the click event, simply double-click the button from Design view, and VS .NET will take you into source-edit mode, where it created a new click method that is waiting for you to add more code.

 

What s not so obvious is that VS .NET just created some code that points the button to that new click code, by delegating its event. Depending on the language you use, the syntax will be different. In VB .NET, part of the code is defined with the object declaration (note the WithEvents keyword above), and part of it is attached to the method definition (note the Handles clause):

 

Private Sub Button1_Click(ByVal sender As System.Object, _

 ByVal e As System.EventArgs) Handles Button1.Click

End Sub

 

In C#, the general idea is fairly similar, although the syntax is a bit different. First of all, you need an event method:

 

private void OurButton1_Click(object sender,

 System.EventArgs e)

{

}

 

Then, the event has to be delegated to that method. This happens in the InitializeComponent method:

 

this.OurButton1.Click += new System.EventHandler(

 this.OurButton1_Click);

 

Now, you can use the created event method to add any VB or C# code you like. Note that at this point, the click code technically doesn t belong to the button anymore, which becomes painfully obvious when you try to copy the button into another form. But at least the code is in the same source file. I ll introduce a more perfect scenario later.

 

When you run this code, ASP.NET displays the Web page, and you can click on the button. Because you are in the same browser-based environment you have been in before ASP.NET, all you can do at this point is navigate to some page. However, as the developer, you don t worry about that at this point. That s because ASP.NET simply navigates to the same page again, and its internal engine realizes that all you want to do is fire the click code and so it fires it. The whole process becomes entirely transparent for you.

 

When the click event fires, you can make a decision about what s to happen next. Perhaps you really want to navigate to a different page (perhaps the button is the Next button of a complex wizard), or you can simply change something on the current page. In the following VB .NET example, you re changing the caption of the button itself:

 

Private Sub Button1_Click(ByVal sender As System.Object, _

 ByVal e As System.EventArgs) Handles Button1.Click

      Button1.Text = "Hello World!"

End Sub

 

This shows another benefit of encapsulation: You don t have to worry about the string that represents the button in the HTML code anymore. You simply set a property. The button takes care of rendering itself, so it makes sense to the browser.

 

Pessimists may say you really didn t accomplish anything you couldn t have accomplished before and that all you did was change the caption of a button. And that s true. Object-oriented development doesn t enable you to do anything you couldn t have done without it, but it makes things much easier. That s why object-oriented programming has been such a success. The question is not whether it is possible to develop advanced applications without objects, but whether such development is feasible.

 

What about Inheritance?

One of the greatest new features of ASP.NET is inheritance. It allows you to abstract objects into classes and build them with entirely new behavior without starting from scratch. In the last example, you built a perfectly functional button. However, it wasn t very reusable. As I pointed out, the code that goes with the click event is delegated to an external function. This means that if you want to reuse the button on another page, you need to write the code all over again. Although you have used encapsulation successfully to get away from the logically disconnected and somewhat chaotic scenario I outlined at the very beginning of the article, you haven t solved the reusability issue. What you really need is a new button that looks and behaves just like any other button, except that it has a different caption and also has standard click behavior. To create such a button, create a new button class that derives from the standard button class. In FIGURE 1, I m using C#.

 

using System;

using System.Web.UI;

using System.Data;

using System.Drawing.Design;

using System.Web.UI.WebControls;

 

 

namespace WebApplication2

{

   public class OurButton : System.Web.UI.WebControls.Button

   {

      public OurButton()

      {

         this.Text = "Test";

         this.Click += new

          System.EventHandler(this.OurClick);

      }

 

      private void OurClick(object sender,

       System.EventArgs e)

      {

         this.Text = "Hello World!";

      }

   }

}

FIGURE 1: A simple sub-classed button with your custom behavior.

 

In this case, the button itself has a method that takes care of the click. Also, the button has a changed caption. This is done in the button s constructor. A constructor is the method that fires immediately when an object is created. In VB .NET, the constructor is implemented as the New subroutine. FIGURE 2 shows the VB .NET version of the same class.

 

Imports System

Imports System.Web.UI

Imports System.Data

Imports System.Drawing.Design

Imports System.Web.UI.WebControls

 

Public Class OurButton

    Inherits System.Web.UI.WebControls.Button

 

    Public Sub New()

        Me.Text = "Test"

    End Sub

 

    Private Sub OurClick(ByVal sender As System.Object, _

        ByVal e As System.EventArgs) Handles MyBase.Click

 

        Me.Text = "Hello World!"

    End Sub

End Class

FIGURE 2: The same button as in FIGURE 1 but coded in VB .NET.

 

Everything else in this button is identical to any other button because you inherit from a regular button. You can imagine inheritance like a very dynamic and flexible copying mechanism. It copies all the code required by regular buttons into your new class and then adds whatever you may have created, which overrides the defaults.

 

All that s left to do is add the button to another Web page (after you compile your new class, of course). Here s the code to accomplish that:

 

 

Your very own button uses a separate namespace that defaults to cc1 (Custom Control 1, I assume). This namespace has to be registered with ASP.NET. You can use the following directive at the top of the ASP.NET page to do that:

 

<%@ Register TagPrefix="cc1" Namespace="WebApplication1"

 Assembly="WebApplication1" %>

 

WebApplication1 is the default namespace assigned to your application. Of course, you could change your namespace to anything you want.

 

As you can see, there is a little more work to do here to make ASP.NET recognize the new button class you created. At this point, it would be much easier to drag and drop a standard button from the toolbox, rather than add the custom button manually. However, you can make your button toolbox-enabled by adding a few attributes to your C# code:

 

[ToolboxData("<{0}:OurBtn runat=server>")]

 public class OurBtn : System.Web.UI.WebControls.Button

{ ... }

 

Again, the VB .NET version is very similar:

 

")> _

 Public Class OurBtn

    ...

End Class

 

After re-compiling the code, you can right-click your toolbox and select Customize Toolbox. In the dialog that pops up, select the .NET Framework tab and browse to your newly compiled file to see the new button class you created. Select it from the list and add it to the toolbox. Voil ! Now, you can use the custom button as easily as you could use the default button. In fact, there isn t much difference between the button you added to the toolbox and the button that was there in the first place. After all, Microsoft derived that button from a more basic Web object (WebControl).

 

Again, some may say that s a lot of work to go through just to set the button s caption. And that s a good point. You wouldn t use inheritance for a scenario like this. Instead, you d go with the standard button and the event delegation. And that s fine! Most event code for Web Forms objects will be handled in the standard objects with simple event delegation. However, if you have an object that s more generic, inheritance makes a lot of sense.

 

Your First Real Derived Object

For instance, consider a drop-down list that shows information from a database. You could encapsulate this kind of functionality in a custom object, as the C# code in FIGURE 3 shows.

 

[ToolboxData("<{0}:ProdDD runat=server>")]

 public class ProdDD :

   System.Web.UI.WebControls.DropDownList

{

   public ProductsDropDown()

   {

      DataSet oDS = new DataSet();

      SqlConnection oCon = new SqlConnection(

       "data source=(local);initial catalog=Northwind;"+

       "integrated security=SSPI;"+

       "persist security info=True;");

      oCon.Open();

 

      SqlDataAdapter oAd = new SqlDataAdapter(

       "SELECT ProductID, ProductName FROM Products",oCon);

 

      oAd.Fill(oDS,"Products");

      oCon.Close();

 

      this.DataSource = oDS;

      this.DataMember = "Products";

      this.DataValueField = "ProductID";

      this.DataTextField = "ProductName";

      this.DataBind();

   }

}

FIGURE 3: The C# version of the specialized products drop-down list.

 

As you can imagine, the VB .NET version in FIGURE 4 is very similar.

 

")> _

Public Class ProdDD

   Inherits System.Web.UI.WebControls.DropDownList

 

   Public Sub New()

      Dim oDS As New DataSet()

       Dim oCon As New SqlConnection( _

       "data source=(local);initial catalog=Northwind;" + _

       "integrated security=SSPI;" + _

       "persist security info=True;")

      oCon.Open()

      Dim oAd As New SqlDataAdapter( _

       "SELECT ProductID, ProductName FROM Products", oCon)

      oAd.Fill(oDS, "Products")

      oCon.Close()

 

      Me.DataSource = oDS

      Me.DataMember = "Products"

      Me.DataValueField = "ProductID"

      Me.DataTextField = "ProductName"

      Me.DataBind()

   End Sub

End Class

FIGURE 4: The VB .NET version of the products drop-down list.

 

In this example, the object queries information from the Northwind Products table in its constructor and populates itself automatically. FIGURE 5 shows this drop-down list in action.

 


FIGURE 5: The derived drop-down list used on a simple Web page.

 

Now, you can drop this object on any Web Form you want and have an instant products drop-down list without writing a single line of additional code. And, if you want to change this drop-down list later (to show only certain countries, for instance), you can do so in one place, and it will change throughout your application. Also, you can give this drop-down list to other developers on your team, and they can use it without having to worry about how the object works (just like you wouldn t worry about how a standard ASP.NET button works). You could even give this object to your colleagues before it is done, so they could proceed with their development. So, even if you are very busy (and who isn t?), you can commit to specific parts of the system without holding up the whole project. Once you finish your class, it automatically will appear correctly on all forms.

 

It s functionality like this that makes inheritance so exciting. However, there is a downside. What if a change you made breaks the class? It potentially could break the entire application at all ends at once. Therefore, you want to give a lot of thought to massive changes you make to your systems. That is why it is so important to plan complex object systems in advance. In most cases, though, it is also relatively straightforward to fix problems you may have caused, because there is only one place where you need to fix them.

 

It is very common for people to cause problems with these global-change scenarios as they attempt their first steps with inheritance. However, these problems are not specific to inheritance. Imagine if Microsoft had broken command buttons in a service pack for VB6. We haven t had any such problems on a large scale because developers usually can handle these scenarios as long as they are aware of them.

 

When people argue that inheritance is too dangerous a methodology to control, I m reminded of the debates about the first automobiles. Would you believe that people argued motorized vehicles were too dangerous because they might run into cows? It seems silly today. Motorists pay a lot of attention to avoid hitting objects, and that s just part of the game. Yes, every now and then, a cow (or worse, a person) does get run over. But, on a whole, the car is still a good thing. And you know what? When I drove to work this morning, I didn t really think about not running into a cow. It s just second nature to a decent driver. However, a large number of accidents involve novice drivers, and the same is true when it comes to object-oriented development and inheritance. You might knock over a virtual tree or two at first, but, ultimately, you will drive much faster with inherited objects than you ever could run without them, no matter how good an athlete you are.

 

Taking It to the Next Level

The products drop-down list was very useful. I can imagine similar lists for anything from countries to sales regions. You simply could create different drop-down lists derived from the default list, as you did in the example shown earlier. But that would result in a lot of coding. You really want to reuse code so that you only have to write it once and can maintain it in only one place.

 

A good solution to this scenario is an intermediate class that isn t useful by itself but that provides common functionality that can be sub-classed into more specific objects. Such a class generally is known as an abstract class (as opposed to a concrete class). FIGURE 6 shows the C# code for the abstract drop-down list.

 

public class AbstractDropDown :

   System.Web.UI.WebControls.DropDownList

{

   protected DataSet oDS = new DataSet();

   protected string FieldList = "*";

   protected string TableName = "";

 

   public AbstractDropDown()

   {

      this.ConfigureObject();

      SqlConnection oCon = new SqlConnection(

         "data source=(local);initial catalog=Northwind;"+

         "integrated security=SSPI;"+

         "persist security info=True;");

      oCon.Open();

 

      SqlDataAdapter oAd = new

         SqlDataAdapter("SELECT "+

            this.FieldList +" FROM "+

            this.TableName,oCon);

 

      oAd.Fill(oDS,this.TableName);

      oCon.Close();

 

      this.DataSource = oDS;

      this.DataMember = this.TableName;

      this.DataBind();

   }

 

    virtual public void ConfigureObject()

   {

   }

}

FIGURE 6: A more generic data drop-down list.

 

As you can imagine, the VB .NET version in FIGURE 7 is similar in concept.

 

Public Class AbstractDropDown

   Inherits System.Web.UI.WebControls.DropDownList

 

   Public oDS As New DataSet()

   Public FieldList As String = "*"

   Public TableName As String = ""

 

   Public Sub New()

      Me.ConfigureObject()

      Dim oCon As New SqlConnection( _

       "data source=(local);initial catalog=Northwind;" + _

       "integrated security=SSPI;" + _

       "persist security info=True;")

      oCon.Open()

 

      Dim oAd As New SqlDataAdapter("SELECT " + _

       Me.FieldList + " FROM " + _

       Me.TableName, oCon)

      oAd.Fill(oDS, Me.TableName)

      oCon.Close()

 

       Me.DataSource = oDS

      Me.DataMember = Me.TableName

      Me.DataBind()

   End Sub

 

   Public Overridable Sub ConfigureObject()

 

   End Sub

End Class

FIGURE 7: Your abstract drop-down list created in VB .NET.

 

In this example, you changed the code so it is more generic and uses properties to create the query string. The code still runs in the constructor of the object. But, before the query executes, it fires another method named ConfigureObject, which surprise does nothing. The idea here is to provide a simple way to add code in subclasses. In fact, the abstract class itself would fail if you tried to use it. However, consider the following derived class:

 

[ToolboxData("<{0}:ProdDD runat=server>")]

public class ProdDD : AbstractDropDown

{

   public override void ConfigureObject()

   {

      this.DataValueField = "ProductID";

      this.DataTextField = "ProductName";

      this.TableName = "Products";

   }

}

 

And the VB .NET version:

 

")> _

Public Class ProdDD

   Inherits AbstractDropDown

 

   Public Overrides Sub ConfigureObject()

      Me.DataValueField = "ProductID"

      Me.DataTextField = "ProductName"

      Me.TableName = "Products"

   End Sub

End Class

 

It derives from the abstract class, which, in turn, derives from the standard drop-down class. That s okay inheritance can go over an unlimited number of levels. In fact, the drop-down class derives from other classes, as well.

 

When the ProdDD class runs, it will do everything its parent class does, which includes running the constructor. In the constructor, the class calls its ConfigureObject method. Now, here is where the power of inheritance really kicks in. Because you overrode that method in your latest class, that code will run rather than the code in the abstract class. This allows you to configure the object appropriately before the actual query runs.

 

With this basic architecture in place, you easily can create another drop-down for a different table; for example, Customers:

 

[ToolboxData("<{0}:CustDD runat=server>")]

 public class CustDD : AbstractDropDown

{

   public override void ConfigureObject()

   {

      this.DataValueField = "CustomerID";

      this.DataTextField = "CompanyName";

      this.TableName = "Customers";

   }

}

 

In VB .NET, it s as follows:

 

")> _

 Public Class CustDD

   Inherits AbstractDropDown

 

   Public Overrides Sub ConfigureObject()

      Me.DataValueField = "CustomerID"

      Me.DataTextField = "CompanyName"

      Me.TableName = "Customers"

   End Sub

End Class

 

In fact, you can even take this a step further and create a special customer class that shows the name of the contact person instead of the company name:

 

[ToolboxData("<{0}:CustDD2 runat=server>")]

public class CustDD2 : CustDD

{

   public override void ConfigureObject()

   {

      base.ConfigureObject();

      this.DataTextField = "ContactName";

   }

}

 

In VB .NET, it s:

 

")> _

Public Class CustDD2

    Inherits CustDD

 

    Public Overrides Sub ConfigureObject()

        MyBase.ConfigureObject()

        Me.DataTextField = "ContactName"

    End Sub

End Class

 

In this example, you override the ConfigureObject method. However, you change only one property, and you want everything else to be the way it was in the customer class. Normally, overriding a method wipes out everything that was there before. But, in this case, you would like to add your code to the existing code. You can do so by specifically calling the code in the parent class (also known as the base class), through the base pointer in C# and the MyBase pointer in VB .NET. You can call the base functionality at any point in the method. In the example, you do it before setting the property to make sure your new code always has the last word.

 

As you can see, inheritance becomes more useful in scenarios that are more complex. This is why the true power of inheritance is sometimes hard to explain.

 

Conclusion

The addition of object-oriented technology to ASP.NET finally has put an end to highly complex and confusing string concatenations. Object-oriented programming features enable the developer to build reusable code and create Web applications that can rival Windows applications in functionality. In this article, I was only able to scratch the surface of object-oriented development. In future articles, I hope to dig much deeper into even more exciting possibilities.

 

Markus Egger is president of EPS Software Corp., located in Houston, TX. He is also the founder of EPS Software Austria, located in Salzburg. He concentrates on consulting in .NET and COM-based, object-oriented development and Internet applications. He is an international author and speaker and is co-publisher of Component Developer Magazine. He is also the author of Advanced Object-Oriented Programming with Visual FoxPro, from Hentzenwerke Publishing. For the past several years, Markus has received the Microsoft MVP award, and several applications on which he has worked (mostly as project manager) have received Microsoft Excellence Award nominations. For more information, visit http://www.eps-software.com/MarkusEgger or e-mail Markus at mailto:[email protected].

 

Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish