How to create ASP applications without writing ASP code

Download the sample project

As far as long-term code maintenance is concerned, ASP could be one of the worst environments ever created. I mean, here we have tons of code where logic is barely separated from content and sometimes not at all. Corporations literally are stuck with millions of lines of hard-to-follow ASP code and they will be paying for it in years to come. Which is great for developers, because it keeps us in business. And don’t get me started on debugging ASP. As one of my co-workers says: "You have to dance on one leg and sing a Gypsy song in order to get Interdev to debug ASP code". So, when Paul Sheriff of PDSA (a well-know VB instructor - see his ad in every VBPJ) showed me a working, easily maintainable web application, totally written in VB (not VBScript), except for 6 lines of ASP code, I was stunned. I had to go through several steps of ASP recovery workshop. First there was bewilderment - why, why, why did I program ASP all this time. Then anger - so much lost time that I will never get back. After anger I experienced curiosity - exactly how does this work and what more can be done with it. The 4th step was acceptance. In the final step I embraced the new paradigm. Now I am more productive than ever. Web applications fly off my computer.

So, how does this work? Let us start with basic facts. You will need one VB project. You will need a separate class for each page. That sounds like a lot of work especially if you have a large site. However, these classes are fairly small with no properties, one method and only several functions. Moreover, the classes are similar to each other, so there is going to be a lot of cut & paste. You will also need one ASP file. As this is where the users first touch your web site, it is here where we start to code. Create a file called exec.asp and add lines below to it.


<%@ Language=VBScript %>
<%
dim oInstance, sClass, sProject

sClass = Request.QueryString("cls")
sProject = Request.QueryString("prj")

set oInstance = Server.CreateObject(sProject & "." & sClass)
oInstance.ProcessRequest
set oInstance = nothing
%>

From here on out, that is all ASP you will ever have to write. Say goodbye to it, spit on it, cuss at it and generally get all the anger out of your system before proceeding.

Let's analyze what this ASP script is doing. It expects two items in the QueryString collection: cls and prj. Then it puts them together to form a class string and attempts to create an object. So for instance, if you type in on the browser line:

http://www.mysite.com/exec.asp?prj=MyProject&cls=View

the ASP file assumes that you have a COM DLL with a class string called MyProject.View.

Futher down, notice line oInstance.ProcessRequest. The ASP file assumes that the created object has a Public method called ProcessRequest. So obviously all the processing will go on in ProcessRequest method.

So far, so good - we need to create a class for each page and there should be a standard Public method called ProcessRequest. Easy enough.

You maybe wondering at this point - how exactly am I going to get access to the ASP intrinsic objects? Well, because our COM object is being created with Server.CreateObject instead of CreateObject, the intrinsic objects will be available (with a little bit of code) in their full glory.

Now create a new ActiveX DLL project. Call the project "MyProject". Call the class "View".

Add the following to the class (you will have to add this to every single class that will be created the ASP script).


Option Explicit
Private
mobjContext As ASPTypeLibrary.ScriptingContext
Private
Const conHTMLTokenPrefix = "{|"
Private
Const conHTMLTokenSuffix = "|}"
Public Sub
OnStartPage(objContext As ASPTypeLibrary.ScriptingContext)
    Set mobjContext = objContext
End Sub

Public Sub
OnEndPage()
    Set mobjContext = Nothing
End Sub

Public
Sub ProcessRequest()
    On Error GoTo errProcessRequest


    'Your code goes here
    '...

 
    exitPoint:

    'generic exit point

    Exit Sub
errProcessRequest:

    'write out the error to the browser

    Call mobjContext.Response.Write(Err.Description)
    GoTo exitPoint
End
Sub

Let's analyze this piece of code. The way ASP's Server.CreateObject works is that it looks for OnStartPage and OnEndPage methods to pass in the ASP context object (intrinsic objects, in other words). Once ASP passes in the object context intrinsic objects (Request, Response, Server, Application, Session) are available under the mobjContext super object. For instance, to write out a line of code you would use the following in the ProcessRequest sub.

mobjContext.Response.Write "My Test Line"

Pretty cool, ha? Keep in mind, you can place breakpoints in your ActiveX DLL and use the full power of the Visual Basic debugger.

Now you are probably thinking, the great thing about ASP was that I could create HTML page in my favorite editor and then intersperse VBScript code into it. Do I have to write HTML out one line at a time using this approach? Absolutely not - it would defeat the whole purpose of Rapid Application Development. In fact, our approach is relatively easy to implement.

We can achieve this ease by use of HTML templates. Let me explain. Let's say we have a page that has to be populated by the list of employees. So our final HTML page would look like this:

HTML Code Preview
<html>
<body>
<h1>Employee List</h1>
<select size="5">
    <option>John</option>
    <option>Mary</option>
    <option>Sam</option>
    <option>Claudia</option>
</select>
</body>
</html>

Employee List

Obviously, we can't just send out a hard-wired list, as our employee list may change. So we then create a template. We replace the hardwired names with a tag, our engine will understand.


<html>
<body>
<h1>Employee List</h1>
<select>
    {|EMPLOYEE LIST|}
</select>
</body>
</html>

So in our code, we will get the list of employees from the database, read in this template, replace the EMPLOYEE LIST tag with our real employee list and send the text out using mobjContext.Response.Write method. Easy enough? But wait, there is more. You can wrap the whole process of reading templates and replacing tags into a reusable class (one is provided with the download).

So here is how the final draft of the ProcessRequest method will look like:


Public Sub
ProcessRequest()
    Dim objTemplate As clsTemplateManager
    Dim strReturn As String

    '******************************************
    'normally this code should be encapsulated
    'in another object or module

    Dim oConn As New ADODB.Connection
    Dim oRs As New ADODB.RecordSet
    Dim sList As String

    'open a connection to an Access database
    oConn.Open Provider="Microsoft.Jet.OLEDB.3.51;;Data Source=" & _
        App.Path & "\Sample.mdb"
    'populate the recordset with the names
    oRs.Open "select * from tblNames", oConn

    'build an HTML list

    Do While Not oRs.EOF
         sList = sList & "<option value=" & oRs!ID & ">" & _
         oRs!Name & "</option>"

         oRs.MoveNext
    
Loop
    Set oRs = Nothing
    Set oConn = Nothing
    '******************************************
 

    'Template Manager

    Set objTemplate = New clsTemplateManager
    With objTemplate
        'Add values and Tokens

        .TokenAdd sList, "
EMPLOYEE LIST"

        'Set Template Name and Tokens

        .TemplatePath = App.Path & "\Templates" & "\SeeName.htm"
        .TokenPrefix = gconHTMLTokenPrefix
        .TokenSuffix = gconHTMLTokenSuffix

        'Combine tokens and template and return HTML page

        strReturn = .WriteResponse
    End With

    ' Write out the page

    Call mobjContext.Response.Write(strReturn)

    exitPoint:

    'generic exit point

    Exit Sub
errProcessRequest:

    'write out the error to the browser

    Call mobjContext.Response.Write(Err.Description)
    GoTo exitPoint
End
Sub

 

Here is a quick explanation on how to get this Setup working on your computer.

  1. I assume you have IIS or PWS - if you don't get it at http://www.microsoft.com/ntworkstation/downloads/RecommEnded/ServicePacks/NT4OptPk/Default.asp
  2. Then under c:\inetpub\wwwroot folder create a new directory called Sample. Unzip the downloaded project into this directory. Double-click on the MyProject.vbp file and click Run.
  3. Open your favorite browser and go to http://localhost/Sample
  4. That's all there is to it. Now you can place breakpoints in your VB code, learn how the code works and debug at will. The templates are location in the \Sample\Templates folder
  5. What does the downloaded program do? Included with the download there is a simple access database With a single table. This table contains a list of employees. This application allows to manipulate the names of employees.

Troubleshooting

  • First and foremost, read IIS and COM Troubleshooting guide. It pretty much covers all the possible situations and how to get out of them.
  • Are you getting Server.CreateObject failed while checking permissions. Access is denied to this object error or some other permission error? These problems occur mostly (99%) with Windows 2000 or Windows XP Server. They are documented (and several solutions are presented) in Microsoft Knowledge Base article Q259725. Basically, after being severely critisized for lack of security in Windows NT, they really went overboard with it in Windows 2000 and later. For instance, out of the box, you can't debug ActiveX DLLs in Visual Basic 6. You can run them fine, but you can't debug them.
    Why does this occur? Because when you debug an ActiveX DLL which is being called from IIS, you are doing so with the IUSR_machinename account. Now, IUSR_machinename account has permissions to run ActiveX DLL, but when you are debugging in the VB IDE, you are really running VB6.EXE which is actually an ActiveX EXE. IUSR_machinename account has no permissions to run ActiveX EXEs. Permissions for ActiveX EXEs are set through DCOM.
    The knowledge base article presents 2 solutions. First solution is a long-term resolution which will allow you to debug any ActiveX DLLs. Second solution will only apply to your current problem. On top of that if your web server is available to the public - it will make everyone log on(!!!), so try the first solution and see if that resolves your problem.
    I actually have an even better and an easier solution to this problem. Simply run your app under an account that has admin rights. What? You are worried about security? This is your development PC, so who cares about security.

  • Once you get past the initial amazement :), you'll want to speed up your development even further. In my work, I have to constantly crank out variuos lists of information, often in a listbox form. So I wrote a control that almost fully emulates the standard VB listbox and adds a Render method that spits out the list in the HTML listbox form. Check it out here.
  • Happy code writing.

    Update

    P.S. A reader emailed me complaining that I haven't listed any of the drawbacks of this method. And as with anything, there are tradeoffs to make in life, so there are some drawbacks. I will list them, each with a quick explanation.

    Performance
    This method yields pretty good performance. Most of the time it is better than ASP or ASP plus some COM Objects. However, don't expect to write the next Yahoo site because the performance will not stand up to it. On a standard server (512 MB RAM, fast HD, PIII 700Mhz or so), you will get pretty good performance with around 30 concurrent (by concurrent I mean people simultaneously requesting a page) connections. The overhead of simultaneously retrieving 30 different HTML templates from the hard drive (or even from RAM once it becomes cached) is enough to bog down the performance. However, once you reach a milestone where you have 30 concurrent users, you should scale out (i.e. get more servers).


    Security
    Another common complaint is that the method exposes class names and that somehow it is dangerous. While I don't consider it a risk, if it makes you uncomfortable, simply mask the class names by changing then in the ASP file. In other words, if the class name is EmployeeDelete, have the URL pointing to exec.asp?cls=Employee&CMD=Delete. In the ASP file, you would have to catch this (using the QueryString collection) and do a Server.CreateObject on QueryString("cls") & QueryString("CMD").

    Hosting
    Jason (jason@catamaranco.com) mentioned this problem and it truly is the only one that actually impedes this method. If you are hosting your application with a hosting provider, chances are they won't allow you to register DLLs on their box. This is not a problem if you are writing your apps for the intranet or for corporations that host their own web sites. So what are you choices if you are in a hosting situation? Well, you can co-locate your own box with the hosting provider. That's more expensive, but the only solution I can think of.