Object Oriented ASP: Using Classes in Classic ASP

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

by Richard Quinn


Do you know how much of a nightmare some ASP pages can be? Do you know how disastrous it is to intermingle HTML with program code? Yes, you do, I know you hate it too! Been there, seen that, you've probably got some cool tricks to reduce the clutter in your own work. Perhaps you use functions extensively, putting away code from out of your HTML and into an include file? Well here's an article showing you how you can take this principle (called information hiding) to a new dimension!

The bookstore example.

Lets take a hypothetical web site, its a library's site detailing information about books! Books are a good example, really, because everyone can see how online bookstores work from a customer perspective.

Books have certain attributes, here I'll list some:

  1. ISBN Number
  2. Author(s)
  3. Title
  4. Subtitle
  5. Number of Pages
  6. Binding Type
  7. Publisher's Name
  8. Published Year

Of course this list is not exhaustive, try here for an exhaustive list of attributes you can collect about books! For our purposes it's enough. If you don't want to store your book data as XML, a relational database is a good idea.

Before looking at how you want to store this information, it's worthwhile modeling the objects first. You can see that I've identified two separate objects, books and authors. Publishers or binding types are certainly other candidates, but they would complicate the example a little too much.

Book Class Diagram
Figure 1: Book Class Diagram

As you can see, both the book and the author classes have a common set of operations: LoadFromID, Store and Delete. Real OO purists would start decomposing the classes here and make them share, or inherit, these operations from another class. But VBScript can't do this, so I'll leave this explanation for another day :) Another necessary feature is the attribute ID. Imagine you have thousands of books and tens of thousands of authors. A convenient way to identify individual entities is by using some form of self-generating ID, which I've modeled here.

Here is a diagram which shows some of the data entities you may consider when modeling your database:

Book Entity Relation Diagram
Figure 2: Book Entity Relation Diagram

The main point to notice is that the data is partially normalized, author data is stored separately from book data. An association table is used to create relationships between books and authors, whereby an author may have written several books, and a book may have been written by several authors. This principle can also be applied to the binding type column or to the publishers name column, in practice you will want to normalize this data too.

You may or may not like the way I name the tables and the columns, but that's your problem :) This scheme has been very useful for me in the past, and I'm sticking with it. Tables are named in the singular, since they represent an entity from the real world. Columns have a prefix denoting the data type of the field: lng: Long Integers, str Strings, dtm: DateTime. The column prefixes are only intended to be useful to humans who can immediately see what data type goes in the field and can make educated guesses about how to use or format the data there. The suffix: ID is reserved for fields which form a primary or foreign key. For a more detailed discussion of primary and foreign keys, indexes and IDENTITY fields, you should see the documentation which comes with your make of database.

An interesting side note is that these diagrams are as equally useful to librarians as they are to programmers or to database administrators. True, each group may get something additional from the diagram, but the terms used and the relationships shown are based on real world scenarios and need little interpretation. Avoid cryptic names and unclear associations, they are more trouble in the long run.

The application.

OK, now we have fairly detailed information about the application we want to build, we've even shown it to the client and had them sign that they agree that it says what they want them to say! So lets get down to building the application, making the Database, the HTML and ASP pages and testing it all! Right? Wrong!

The first thing we should be doing is building the classes which represent the two objects we've identified. This well help is to clarify what we're doing, any changes we come up when building the objects can be easily accommodated, since neither the database nor the ASP pages yet exist, and therefore do not need to be re engineered.
I'm assuming you've heard of VBScript classes, and know that they are formed within this code construct:

Class cClassName
'some code....
End Class

OK, here comes an example attribute of the book class, its ISBN number:

'Private, class member variable
Private m_ISBN
'Read the current ISBN value
Public Property Get ISBN()
      ISBN = m_ISBN
End Property

'store a new ISBN value
Public Property Let ISBN(p_Data)
      m_ISBN = p_Data
End Property

This was the ISBN attribute. The actual value of the ISBN is stored in a private variable, called m_ISBN. Here I also use prefixes to identify the variable as a member of the class. Doing this allows us to check that values written to or read from the class are never badly formatted or just plain wrong. They are doorways to the hidden insides of the class, we can check what's coming in and going out. Here is the partial book class containing all of its attributes.

Now I can show you an example of how the class could be used. This is old style, questionably useful ASP code mixed in with the HTML:

<tr><TD><%=MyRecordest.Fields(1).Value%></td></tr>

Of course, you needed reams and reams of supporting code to build the connection, get the data out of the DB and loop through the records, outputting the values directly into the HTML. Well here is how we create an object from our book class:

<% Set MyBook = new cBook %>....
<tr><TD><%=MyBook.Title%></td></tr>

You can see an improvement in the code already, it is self describing. You can see immediately that the book's title is being placed in within the table cell, no more remembering what is in which field of your recordset. Also, of course, if the book doesn't yet have a title, you might need to do something like this in old-style ASP:

<tr><TD><%=
   strTitle = MyRecordest.Fields(1).Value
   if strTitle = "" then strTitle = "no title yet"
   response.write strTitle
%></td></tr>

In an OO approach, you can put this behaviour inside the class, specifically, in the property attribute accessor method:

Public Property Get Title()
    If m_Title = "" then
         Title = "not title yet"
    Else
         Title = m_Title
    End If
End Property

This cleanly separates the business rules ("if there is no title display a message saying so") from the presentation of your information. Your improved ASP code:

<tr><td><%=MyBook.Title%></td></tr>

...will conform to the business requirements without messing up your presentation. If the business rules change, and they often do, you can make the modification in one location within your class, and not hunt through every HTML page for every instance of where you have printed the title. Life is starting to get much easier, but there is more!

You run into real difficulties when your DB code for reading, updating and deleting the data is repeated all through your web site, whereever you needed to display it. If you change the database you may need to modify lots of web pages to reflect the changed database structure. Wouldn't it be easier if the DB code were stored directly in the object? You would only need to write it once, store it in your class file and reuse it wherever you use those objects in your web site? In the object diagram above, the place to store this code is in the operations LoadFromID, Store and Delete. I'll show you first how this functionality can be easily used in your web pages, and then how it is implemented in the class file.

Creating and loading a book object in ASP, then displaying the book's information within HTML:

<%
'Create an empty book object,
'Load the object's data based on an ID you get from the request,
'Display the books's information.
Set MyBook = new cBook
MyBook.LoadFromID(Request("bookid"))
%>....

<tr><TD>Title: <%=MyBook.Title%></td></tr>
<tr><TD>SubTitle: <%=MyBook.SubTitle%></td></tr>
<tr><TD>Publisher: <%=MyBook.PublishersName%></td></tr>
<tr><TD>Year: <%=MyBook.PublishedYear%></td></tr>
<tr><TD>Format: <%=MyBook.BindingType%></td></tr>
<tr><TD>ISBN: <%=MyBook.ISBN%></td></tr>

No database code, no worries, everything is hidden away in the implementation of the LoadFromID operation.
I have created a new database for this example using Microsoft Access, "vboo_pubs", based on the example pubs database in SQL Server. It has 3 tables which interest us, book, author and authortobook, which happily directly correspond to the three tables in the entity relationship diagram above. This is how I would implement the LoadFromID operation, using the vboo_pubs sample database:

Public Function LoadFromId(p_Id)
Dim rs, conn, strSQL
Set Rs = Server.CreateObject("ADODB.Recordset")
Set conn = Server.CreateObject("ADODB.Connection")

conn.open "vboo_pubs"

strSQL = " SELECT T.title_id, T.title, T.type, "
strSQL = strSQL & " T.pub_id, P.pub_name, T.pubdate, T.notes "
strSQL = strSQL & " FROM titles T INNER JOIN publishers P ON T.pub_id = P.pub_id "
strSQL = strSQL & " WHERE (T.title_id = '" & p_Id & "') "

rs.open strSql, conn, 1,3

if rs.recordcount =1 then
Me.ID = rs.fields("title_id").Value
Me.Title = rs.fields("title").Value
Me.PublishersName = rs.fields("pub_name").Value
Me.PublishedYear = rs.fields("pubdate").Value
Me.BindingType = "" ' No data available!
Me.SubTitle = rs.fields("notes").Value
Me.ISBN = rs.fields("pub_ID").Value
LoadFromId = Me.ID
mvarSaveOnTerminate = true
else
err.raise 1, "Item with ID " & p_ID & " was not found"
End if

rs.close
Set rs = nothing
conn.close
Set conn = Nothing
End Function

To get this example working you need to have an ODBC data source set up named "pubs", which the object uses to connect to the database. You may have noticed that there is no error handling in this code, this is purposely bare - bones so that you can deal with errors your self. The code actually raises an error if it can't find the record being searched for, this is for your web pages to deal with appropriately. Sometimes not being able to fin a record is unimportant, other times it is critical. The class does not know in which circumstance it is being used, and makes no assumptions about how to deal with errors.

The LoadFromID operation is essential if you want to get the data from your database into the object, and you have an ID. But what if you don't have an ID, but lets say the ISBN? Well, you would create a LoadFromISBN function. Since the two functions are different only in the SQL statement they produce, it would make sense to start refactoring here, and put the database access code somewhere separate from the SQL generation code. Here is the refactored LoadFromID along with the LoadFromISBN operation.

'Loads this object's values by loading a record based on the given ID
Public Function LoadFromId(p_Id)
dim strSQL, MyID
MyID = clng(p_Id)
strSQL = "SELECT lngBookID, strTitle, strSubTitle, strISBN, strBindingType, "
strSQL = strSQL & " strPublishersName, strPublishedYear, strPageCount FROM Book "
strSQL = strSQL & " WHERE (lngBookID = " & MyID & ") "
LoadFromId = LoadData (strSQL)
End Function

'Loads this object's values by loading a record based on the given ISBN
Public Function LoadFromISBN(p_Id)
dim strSQL
strSQL = "SELECT lngBookID, strTitle, strSubTitle, strISBN, strBindingType, "
strSQL = strSQL & " strPublishersName, strPublishedYear, strPageCount FROM Book "
strSQL = strSQL & " WHERE (strISBN = '" & SingleQuotes(p_Id) & "') "
LoadFromISBN = LoadData (strSQL)
End Function

Load Functions

The LoadData function is quite complicated. Since this is not a tutorial about database connections, I have left it to be shown in the complete class listing. Both of these functions return the ID of the book which has been loaded.

The Store and Delete operations do exactly what you expect. Suppose you want to create a new record in the database, how would you do this in an OO way in ASP? This is very easy to do, the hard part is coding the SQL statement, so I'll do that for you. The interesting thing to note here, is that the Store operation is called not just when creating a new database record, but also when saving changes which may have been made to an existing record. You can now do inserts and updates from a single ASP page entitled "Create / Update Book Information":

  1. If the page is called with a book ID, load the book object, otherwise create an empty book object
  2. Show the editing form, and fill it with the values from your book object. The fields are either empty or they show the existing data
  3. When the user submits the form, again see if you have received an existing ID. If yes, load that book object, otherwise create an empty book object
  4. Assign the form values to the object's properties
  5. Call store on your book object. It runs an update or an insert operation automatically

Here is the Store operation:

Public Function Store()
Dim strSQL
'If the book has an existing (Autogenerated!) ID, then run an insert
if Me.ID < 1 then
Dim ThisRecordID, arr1, arr2
arr1 = Array("strTitle", "strSubTitle", "strISBN", "strBindingType", "strPublishersName", "strPublishedYear", "strPageCount")
arr2 = Array(Me.Title, Me.SubTitle, Me.ISBN, Me.BindingType, Me.PublishersName, Me.PublishedYear, Me.PageCount)
Me.ID = InsertRecord("Book", "lngBookID", arr1, arr2)
'Otherwise run an update
else
strSQL = strSQL & " UPDATE Book SET "
strSQL = strSQL & " strTitle = '" & Me.Title & "',"
strSQL = strSQL & " strSubTitle = '" & Me.SubTitle & "',"
strSQL = strSQL & " strISBN = '" & Me.ISBN & "',"
strSQL = strSQL & " strBindingType = '" & Me.BindingType & "',"
strSQL = strSQL & " strPublishersName = '" & Me.PublishersName & "',"
strSQL = strSQL & " strPublishedYear = '" & Me.PublishedYear & "',"
strSQL = strSQL & " strPageCount = '" & Me.PageCount & "'"
strSQL = strSQL & " where lngBookID = " & Me.ID
RunSQL strSQL
End if
Store = Me.ID
End Function

And here is the delete:

Public Function Delete
strSQL = "DELETE * FROM Book WHERE lngBookID = " & Me.ID
RunSQL strSQL
End Function

So we've done the hard work, and made the class. Hard work? Yes and no. It will initially seem like a lot of work, defining the member variables, writing attributes to access them, then writing the DB code and testing it all (hopefully using a unit testing framework like ASPUnit). And all this without any HTML, no web pages, nothing. But that's the beauty! Now you can give a HTML Dude a list of the Objects attributes, and he can make the HTML pages, whilst you go on to make and test the next class file! And of course, because each class file looks almost exactly the same as the previous one, you can automate the production of them! My favourite is to define an XML structure containing all of the attributes of the class, and then write an XSL to transform the XML into a valid VBScript class. Making this kind of system shouldn't take more than a day, writing the XML files for your application's classes no longer than a further day, leaving you weeks and weeks to make funky HTML :)

So here is some of that funky HTML. Earlier, I showed a simple page used to display a book object using HTML. Here is the page which will allow us to edit an existing book object or to create a new one:

<!-- #INCLUDE virtual="/zzTest/cBook.asp" -->
<!-- #INCLUDE virtual="/zzTest/cAuthor.asp" -->
<!-- #INCLUDE virtual="/zzTest/cLibrary.asp" -->
<!-- #INCLUDE virtual="/zzTest/i_helper.asp" -->
<%

dim MyBook
Set MyBook = new cBook

if request("bookid")<>"" then
mybook.loadfromid(request("bookid"))
end if

if request("cmd")<>"" then
Mybook.ISBN = Request.Form("isbn")
Mybook.Title = Request.Form("Title")
Mybook.SubTitle = Request.Form("SubTitle")
Mybook.ISBN = Request.Form("isbn")
Mybook.BindingType = Request.Form("BindingType")
Mybook.PublishersName = Request.Form("PublishersName")
Mybook.PublishedYear = Request.Form("PublishedYear")
Mybook.PageCount = Request.Form("PageCount")
mybook.store
end if

%>

<HTML>
<HEAD>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#FF0000" VLINK="#800000" ALINK="#FF00FF" BACKGROUND="?">
<form name="form1" method="post" action="edit_book.asp">
<table border="1" cellspacing="0" cellpadding="0">
<tr>
<td width="134">Book ID</td>
<td width="12"><input type="hidden" name="cmd" value="edit"></td>
<td width="598"><input type="hidden" name="bookid" value="<%=Mybook.ID%>"><%=Mybook.ID%></td>
</tr>
<tr>
<td>ISBN</td>
<td>&nbsp;</td>
<td><input size="50" name="isbn" value="<%=htmlencode(Mybook.ISBN)%>"></td>
</tr>
<tr>
<td>Title</td>
<td>&nbsp;</td>
<td><input size="150" name="Title" value="<%=htmlencode(Mybook.Title)%>"></td>
</tr>
<tr>
<td>SubTitle</td>
<td>&nbsp;</td>
<td><input size="150" name="SubTitle" value="<%=htmlencode(Mybook.SubTitle)%>"></td>
</tr>
<tr>
<td>Binding Type</td>
<td>&nbsp;</td>
<td><input size="50" name="BindingType" value="<%=htmlencode(Mybook.BindingType)%>"></td>
</tr>
<tr>
<td>PublishersName</td>
<td>&nbsp;</td>
<td><input size="50" name="PublishersName" value="<%=htmlencode(Mybook.PublishersName)%>"></td>
</tr>
<tr>
<td>Published Year</td>
<td>&nbsp;</td>
<td><input size="50" name="PublishedYear" value="<%=(Mybook.PublishedYear)%>"></td>
</tr>
<tr>
<td>´Number of pages</td>
<td>&nbsp;</td>
<td><input size="50" name="PageCount" value="<%=htmlencode(Mybook.PageCount)%>"></td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp; </td>
<td><input type="reset" name="Submit2" value="Reset"> <input type="submit" name="Submit" value="Submit"></td>
</tr>
</table>
</form>

<a href="./default.asp">List</a>
</BODY>
</HTML>

You can download the whole project here.

Navigating from Authors to Books.

As we saw above, instantiating a book object and loading its attributes based on an ID number is very simple. But how do we navigate from our book object to the authors who wrote the book? Well, if we look at the class diagram above, the "0..*" text on the line between books and authors says that a book refers to zero or more authors. Also, an author has a relationship with zero or more books. Strictly speaking, we don't really want to be managing the data of authors who haven't written anything in our library, but the situation occurs often in real life, when for example the author's first book has not yet been published.

The book object has a property, called "Authors". Using object notation we can reference the first author in the collection of the books authors like this: Mybook.Authors.Items(1).LastName

This is how the property is built:

Public Property Get Authors()
if not isobject(m_Authors) then
set Library = New cLibrary
Call Library.GetAuthorsByBookID(Me.ID)
set m_Authors = Library.Authors
Set Library = nothing
end if
Set Authors = m_Authors
End Property

This uses the "Lazy Load" design pattern. The book object does not contain any authors until you actually request author information. The property checks to see if the m_Authors collection has been initialised, if not it initialises a new Authors object. The Authors collection is provided by the library. The library does not have a persistent representation in the database, it has no attributes which we want to display just yet. It does have a large number of operations, representing the functions that a library performs.

In our case, the operation "Library.GetAuthorsByBookID(Me.ID)" asks the library to return all of the authors associated with a particular book as a collection. The library uses some simple SQL to retrieve the requested records from the database. For each record returned, the library creates an author object and initialises the author's attributes. The author object is then added to the collection. The library informs the book about the number of authors created, and stores the collection in its "Authors" property. The book requests the Authors from the library object then stores the author collection internally for speedy future access: "Set m_Authors=Library.Authors()".

Navigating from Authors works in exactly the same way, the Author object has a property called "Books" which represents all the books written by the author. Requesting "Author.Books" returns a collection object, internally the library services the request by retrieving the data from the database, creating book objects and returning them as a collection.

It may be becoming clearer now, that writing ASP classes to model the real world is a lot of effort up-front. This effort can be justified:

  • The classes produced are independent of HTML.
  • The classes can be thoroughly tested and verified before being used by your HTML people.
  • The classes can be coded parallel to screen design or HTML prototyping activities, allowing previously sequential project activities to be performed in parallel.
  • The SQL is hidden away where the DBA / Developer can find it, the designer never sees it.
  • The code produced is reusable. In the real world this small example may take up to a whole day to code and test, but can then be used by several people to actually build a real world application.
  • Your users get a higher quality application (faster, more reliable, more easily modified, errors are localized more quickly, easily maintained, fewer errors).

Discussion

This tutorial has given you an insight into how ASP classes are built. Attributes and operations have been discussed. Navigation between classes has been explored as one of the fundamental abilities of Object-Based systems. The classes presented here use an "Active Record" persistence pattern, each object represents one record in an associated table in the database. The Library object was introduced as a non persistent class which services the request of other objects.

This tutorial presents a domain model. That is, real world objects and their associations are called the problem domain, our database, classes and web pages are part of the solution domain. Our objects closely resembled real world objects. There are other ways of architecting applications than by using a domain model, but this way really does leverage the power of object based systems.

The book and Author classes are responsible for instantiating their own data, but as you may have seen, the library class also instantiates books and authors en masse when creating collections. This is a conceptually "dirty" way of doing things. It would be much nicer if the code for assigning field values to object attributes were only in one place, the risk of bugs would be much reduced. Unfortunately, the need for performance means that an entire table of records must be initialised in a single step.

On the performance side, things could be improved dramatically. Each book is instantiated separately from each author. A minimum of two database calls are necessary to first retrieve the book data and then retrieve the authors data. Folding this into a single call with responsibility for instantiating both book and author data would bring noticeable performance and scalability advantages.

There is a questionable resolution of the problem originally presented: separation of code and HTML. In order to output the object's attributes, a fair amount of scripting is still necessary. True, we have moved away from scripting the database and the HTML to scripting objects and HTML, but a cleaner separation could be achieved if each object had a "Render(OutputFormat)" operation which could make a visual representation for display in the desired output format, for example HTML.

For a fuller understanding of how the navigation between books and authors is implemented, a good look at the accompanying code is essential. There are some tricks and goodies in there which will surprise and delight even the most advanced of ASP authors :)

You can download the whole project below.



About the Author

From ASP101

Articles originally posted on ASP101.com

Downloads

Comments

  • :)

    Posted by Mo on 12/03/2012 09:09am

    Absolutely fantastic! Thanks for the effort. Keep up the good work. :)

    Reply
  • Downloads missing

    Posted by RP on 05/02/2012 02:45pm

    Unfortunately the example files/source samples are no longer available...

    • Downloads are not missing

      Posted by Ahmad Dzulfikar Adi Putra on 06/21/2017 06:13pm

      I know it's old, but the projects file is available to download.

      Reply
    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date