Click here to Skip to main content
15,860,943 members
Articles / Web Development / HTML

Classic ASP and MVC

Rate me:
Please Sign up or sign in to vote.
4.84/5 (63 votes)
21 Jun 2016GPL314 min read 165.6K   10.7K   122   4
MVC-like for classic ASP.

(Optional: Download CreateDatabase.zip)

Unzip the code of the article into a new Virtual Directory test_mvc (http://localhost/test_mvc/) on your IIS server. Make sure you have enabled ASP scripts for the virtual directory. Make sure the default.asp is a default web page for this Virtual Directory.

A sample of application is here

Introduction

How many companies are still extensively using ASP this days and why? Typically the case is a company with huge ASP system in the core and few developers who know the system from it’s childhood. The system is usually too complex, with spaghetti-like style of code inside (because of a nature of ASP). I believe there is a little or no ways for new developers to be involved in support or development of the code. The reason is that the threshold for new developers is too high:

  • market encourages developers to learn new stuff and forget old ones.
  • huge spaghetti-like code makes the effort to study the code meaningless.

These ASP systems exist because they work. Existing developers may want to change something, but the the threshold for them is high too:

  • high workload with existing app support
  • necessity to study new languages (like VB.NET or C#) and technologies (like MVC)

What is easier for ASP developer: to write a new code with C#+MVC or continue with ASP?

Microsoft has dropped the support of ASP and there is a little of innovations this days. Some use JavaScript, some use jQuery, XML but that is it. There is very little of innovations for the server-side that may make the life of programmers easier.

The aim of this effort is to bring MVC concept to ASP. To give a way to write/re-write the ASP app in MVC-like style. Untangle spaghetti-like code. To let newcomers recognize the common code-patterns and pick up the support of existing ASP apps easier.

Background

There is a MVC out there. Let’s embed MVC patterns into the classic ASP.

Router

In the core of MVC there is a Router. The Router selects the Controller and Action (mostly).

Router is a code that decides which Controller and Action to call.

Let's study the Router with comparison MVC and classic scripting.

The IIS internal process locates the folder and file and executes it.

How URLs are processed.
with classic ASP, WebForms, PHP etc. with MVC pattern
Image 1 Image 2
The IIS internal process locates the folder and the file and executes it. The IIS passes the execution to Router. Router locates the Class and Function and executes it.

There will be a discussion later on how to make the nice URLs with MVC pattern.

Samples of URLs (controllers are highlighted)

  • Http://localhost/Home/Index
  • Http://localhost/Home/About
  • Http://localhost/User/Edit/1

Controller

What is this Controller? Why should you care to have Controllers in your code? I may say that it is a significant part of the MVC concept, but these words mean nothing.

Let’s go from practice. It’s rare, but suppose you have a document to develop a part of some system:

Image 3

Where are you going to store the code for this logic? As an ASP developer you would probably create a set of pages to pull and display some data from the database, and a couple of pages to receive Forms with user input to update the database back. It is OK when the system is not too large. It is OK when the User interacts with the system in a simple way.

How is about that system?

Image 4

You will have to create some sort of "Architecture" in your code to do not get totally lost in the code when the system is finished. When I say "Architecture" I mean some repetitive patterns of the code to handle the similar situations. Anyways the system will be a mess of HTML, SQL, code to access the database, handle user input, and display data at the end of development. By the way, I believe that the system on the picture is VERY SIMPLE. What happened if it is 10-20 times bigger?

A remedy for the problem is the code encapsulation with the repetitive standard structures of the code and the special agreement regarding the naming of the components. So there is a place for a special component - a Controller, which interacts with the user and contains a sort of the business logic:

Image 5

Controller is a class. It holds all the aspects of user interaction mostly regarding ONE type of business entity. There should be a number of controllers in the Application. Controllers must have names ending with Controller: HomeController, PublicationController, StatusController, UserController. Controller should not have a user interface code or HTML markup. It is a good practice to avoid the SQL in the controller too. Controller should handle user input, query and update the object model and prepare the data to be displayed. All Controllers should reside in the folder /Controllers.

Controller handles the user input , do the things and controls what is going to be displayed (but not how).

Controller should have methods that compose the logic of Controller. Like this:

VBScript
Class PublicationController
 Dim Model
 
 Public Sub List ()
    ...
  End Sub
  Publis Sub Edit (vars)
    ...
  End Sub
  Publis Sub Delete(vars)
    ...
  End Sub
...
End Class  

These methods of Controller are Actions. Router should be able to call them.

These are samples of URLs with Actions are highlighted.

  • Http://localhost/Home/Index
  • Http://localhost/Home/About
  • Http://localhost/User/Edit/1

Router disassembles the URL and calls one of the Controllers and one of the Actions within the Controller.

There is no standard Router for ASP. It will be a default.asp to work as a Router.

The URLs we are going to employ will be similar to:

  • http://localhost/test_mvc/default.aspx?controller=Home&action=Index
  • http://localhost/test_mvc/default.aspx?controller=Home&action=About
  • http://localhost/test_mvc/default.aspx?controller=User&action=Edit&id=2

or their shorter form (if you have selected default.asp as a default page for the virtual directory):

  • http://localhost/test_mvc/?controller=User&action=List

There is no Reflection in the VBScript and we don’t know whether there is a Controller class and it’s Action method out there. And we don’t need to.

Router simply calls the Action method of the Controller object with VBScript Eval instructions.

So the these two are equal:

VBScript
Set controllerInstance = New HomeController  


controller = "Home"  
Set controllerInstance = Eval("New "+ controller + "Controller")    

But the second one is more flexible and can be used to route the HTTP request based on the controller name. The Action is selected using the same technique.

The code of Router: {text of defaut.asp}

VBScript
<!--#include file="utils/utils.inc" -->
<!--#include file="models/models.inc" -->
<!--#include file="controllers/controllers.inc" -->
<%
Const defaultController = "Home"
Const defaultAction = "Index"

    If not Route () then
        result = RouteDebug ()
    End If


Function ContentPlaceHolder()
    If not Route () then
        result = RouteDebug ()
    End If
End Function


Function Route ()

    Dim controller, action , vars
    controller  = Request.QueryString("controller")
    action      = Request.QueryString("action")
    set vars        = CollectVariables()
    Route = False

    If IsEmpty(controller) or IsNull(controller) then
        controller = defaultController
    End If
    
    If IsEmpty(action) or IsNull(action) then
        action = defaultAction
    End If

    Dim controllerName
    controllerName = controller + "Controller"
    Dim controllerInstance 
    Set controllerInstance = Eval ( " new " +  controllerName)
    Dim actionCallString 
    If (Instr(1,action,"Post",1)>0) then
        actionCallString = " controllerInstance." + action + "(Request.Form)"
    ElseIf Not (IsNothing(vars)) then
        actionCallString = " controllerInstance." + action + "(vars)"
    Else
        actionCallString = " controllerInstance." + action + "()"
    End If
    Eval (actionCallString)
    Route = true
End Function

Function RouteDebug ()
    Dim controller, action , vars
    controller  = Request.QueryString("controller")
    action      = Request.QueryString("action")

    Response.Write(controller)
    Response.Write(action)
    
    dim key, keyValue
    for each key in Request.Querystring
        keyValue = Request.Querystring(key)
        'ignore service keys
        if InStr(1,"controller, action, partial",key,1)=0 Then
            Response.Write( key + " = " + keyValue )
        End If
    next
    
End Function

Function CollectVariables
    dim key, keyValue
    Set results = Server.CreateObject("Scripting.Dictionary")
    for each key in Request.Querystring
        keyValue = Request.Querystring(key)
        'ignore service keys
        if InStr(1,"controller, action, partial",key,1)=0 Then
            results.Add key,keyValue 
        End If
    next
    if results.Count=0 Then
        Set CollectVariables = Nothing
    else 
        Set CollectVariables = results
    End If
End Function

%>

Actions

So the Controller is instantiated and one of it's Actions is called. Next, Action executes the business logic. There may be a business logic of any kind in the controller. You may query or update the database, send e-mails, process files or user input, or even launch a rocket to Mars. Typically there is a logic to update/insert/delete/list records from/to database, but it may be literally anything there. There should be NO HTML markup in the Controller.

At the end of Action, if you want to display soemthing (besides the static content of View) you need to initialize the variable Model and include the View as the last line of the Action.

Look at the example below: If the user hits the link http://localhost/test_mvc/?controller=User&action=List , Router selects the UserController and List action. The List action prepares the list of Users and assign this list to the variable Model. Variable Model is used in the View ../views/User/List.asp This view is attached at the end of action List.

{ text of /Controller/UserController.asp }

<% 

class UserController
 Dim Model
  
 private sub Class_Initialize()
 end sub

 private sub Class_Terminate()
 end sub

 public Sub List()
    Dim u 
    set u = new UserHelper
    set Model = u.SelectAll
 
    %>   <!--#include file="../views/User/List.asp" --> <%
 End Sub
 
 public Sub Create()
    set Model = new User
    %>   <!--#include file="../views/User/Create.asp" --> <%
 End Sub
 
  public Sub CreatePost(args)
    Dim obj, objh
    set objh = new UserHelper
    set obj = new User

    obj.FirstName = args("FirstName")
    obj.LastName = args("LastName")
    obj.UserName = args("UserName")
    obj.ProjectID = args("ProjectID")
    'form values should be cleaned from injections
    'checkboxes shoud use the syntax: obj.ProjectID = (args("ProjectID") = "on")
    obj.Id = objh.Insert(obj)
 
    Response.Redirect("?controller=User&action=list")
 End Sub

 
 public Sub Edit(vars)

    Dim u
    set u = new UserHelper
    set Model = u.SelectById(vars("id"))
 
   %>   <!--#include file="../views/User/Edit.asp" --> <%
 End Sub
 
 public Sub EditPost(args)
    Dim obj, objh
    set objh = new UserHelper
    set obj = objh.SelectById(args("id"))

    obj.FirstName = args("FirstName")
    obj.LastName = args("LastName")
    obj.UserName = args("UserName")
    obj.ProjectID = args("ProjectID")
    'form values should be cleaned from injections
    'checkboxes shoud use the syntax: obj.ProjectID = (args("ProjectID") = "on")
    objh.Update(obj)
 
    Response.Redirect("?controller=User&action=list")
 End Sub

 
 public Sub Delete(vars)
    Dim u
    set u = new UserHelper
    set Model = u.SelectById(vars("id"))
   %>   <!--#include file="../views/User/Delete.asp" --> <%
 End Sub
 
 
  public Sub DeletePost(args)
    Dim res, objh
    set objh = new UserHelper
    res = objh.Delete(args("id"))
    if  res then
        Response.Redirect("?controller=User&action=list")
    else
        Response.Redirect("?controller=User&action=Delete&id=" + CStr(args("id")))
    end if
 End Sub


 public Sub Details(vars)
    Dim u
    set u = new UserHelper
    set Model = u.SelectById(vars("id"))

   %>   <!--#include file="../views/User/Details.asp" --> <%
 End Sub
 
 End Class
%>

Note: as opposed to NET MVC, where View is selected automatically, we need to directly point to the needed View. View should have the same name as Action. They reside in the folder with the same name as the Controller. For example: /Views/Home/Index.asp, /Views/User/Edit.asp.

Views

View provides appearance. It is only Views should have an HTML markup inside them. There should be NO or absolute minimum of the logic in a View. The business of a View is to take the Model and display it to the user in the best way.

{ text of /Views/User/List.aspx }

ASP.NET
 List Users

<%=Html.ActionLink("Create new User", "User", "Create" , "") %> <br/>

<table>
    <tr>

        <td>FirstName</td>

        <td>LastName</td>

        <td>UserName</td>

        <td>ProjectID</td>

        <td></td>
    </tr>

    <%
    if  IsNothing(Model) then
        %> <tr><td colspan="4">No records</td> </tr><%
    Else
        Dim obj
        For each obj in Model.Items
        %>

        <tr>

            <td><%=Html.Encode(obj.FirstName) %></td>

            <td><%=Html.Encode(obj.LastName) %></td>

            <td><%=Html.Encode(obj.UserName) %></td>

            <td><%=Html.Encode(obj.ProjectID) %></td>

            <td>
                <%=Html.ActionLink("Edit", "User", 
                  "Edit" , "id=" + CStr(obj.Id)) %> |
                <%=Html.ActionLink("Delete", "User", 
                  "Delete" , "id=" + CStr(obj.Id)) %> |
                <%=Html.ActionLink("Details", "User", 
                  "Details" , "id=" + CStr(obj.Id)) %>
                
            </td>
        </tr>
        <% 
        Next
     End If
     %>
</table>

So why are these Views so amazing? It is all in the Order and Separation. Design your Views separately from your code. Express Yourself. Change the appearance as easy as 1-2-3. Have a fullscreen or mobile view (or both) without necessity to touch the business logic code. Concentrate on the business logic and data when you work with Controller/Action. Scale your system up to dozens Controllers and hundreds of Views. Enjoy the transparency and easy code navigation. Get rid of spaghetti-like code forever.

Data

Data for ASP projects is usually stored in the database. As an ASP programmer you probably have some data-access utilities and mix them with SQL and HTML in your ASP pages.

They use a term Model to refer the data in MVC projects.

Model

There are broader and narrower meanings of Model term when we talk about MVC.

The broader meaning of the Model.

Usual application has a set of Business objects to work with the database. This set of classes composes a broader meaning of Model or Data Model. Business objects often are stored in Classes and use the Active Record pattern.

{a sample of class diagram}

Image 6

When the data is needed from the database, one row is fetched from table into the object:

  • The object of the corresponding class is created
  • data is fetched from the database and assigned to every attribute of the object
  • the ready object is passed to the business logic.

{code fragments to fetch the data and initialize the User object}

Class User
  private mId
   private  mFirstName
...

     public property get Id()
           Id = mId
    end property

     public property let Id(val)
           mId = val
    end property

     public property get FirstName()
           FirstName = mFirstName
    end property 
    public property let FirstName(val)
           mFirstName = val
    end property 
...

End Class 'User
class UserHelper
...
   public function SelectAll()
     objCommand.CommandText = "Select * from [User]"
     set records = objCommand.Execute
     if records.eof then
              Set SelectAll = Nothing
          else
              Dim results, obj, record
              Set results = Server.CreateObject("Scripting.Dictionary")
              while not records.eof
                      set obj = PopulateObjectFromRecord(records)
                      results.Add obj.Id, obj
                      records.movenext
              wend
              set SelectAll = results
              records.Close
     End If
   end function

   private function PopulateObjectFromRecord(record)
     if record.eof then
         Set PopulateObjectFromRecord = Nothing
     else
          Dim obj
          set obj = new User
          obj.Id                       = record("Id")
          obj.FirstName = record("FirstName") 
          obj.LastName = record("LastName") 
          obj.UserName = record("UserName") 
          obj.ProjectID = record("ProjectID")
          set PopulateObjectFromRecord = obj
     end if
   end function

end class 'UserHelper

When we need to save the data of object into the database, the reverse process is performed: the SQL Update query is executed with attributes of the object and it's ID.

If there is a need to process several records, then the objects are joined into the List or Dictionary.

It is very easy to generate skeletons of classes for your project with the basic set of database operations.

The advantage of this approach is the ability to split the Data Model from the HTML (appearance) and from the business logic (Controller/View). Reuse the data-access code. Consistent SQL code in a single place.

The narrower meaning of Model.

When the Controller/Action perform the business logic (read/write the database, process user input etc) they may need to display something in the corresponding View. So the Controller prepares the Model to pass it to the View. This is a narrower meaning of Model. The representation of of the narrower Model term is a variable that has the same name Model, but may hold specific types of the value for every specific Action-View pair.

For example: If we need to display a list of users the controller: UserController and action: List are called.

{router calls the action}

Set controllerInstance = Eval ( "new UserController")
 Eval ( "controllerInstance.List() ")

CONTROLLER/ACTION call the Data Model :

class UserController
  Dim Model
 ...
  public Sub List()
      Dim u 
      set u = new UserHelper
      set Model = u.SelectAll
  
   %>   <!--#include file="../views/User/List.asp" --> <%     
  End Sub

The the records from "User" table are fetched from the database. Objects of Class: User are created and stored in the List or Dictionary. The List or Dictionary is assigned to the variable Model.

The variable Model is accessible from the code of the VIEW.

So when the ACTION includes the VIEW, it displays the Model as a list of User objects.

{ code of the view /Views/User/List.asp}

List Users
<table>
    <tr>
        <td>FirstName</td>
        <td>LastName</td>
        <td>UserName</td>
        <td>ProjectID</td>
    </tr>
    <%
    if  IsNothing(Model) then
        %> <tr><td colspan="4">No records</td> </tr><%
    Else
        Dim obj
        For each obj in Model.Items
        %>
        <tr>
            <td><%=Html.Encode(obj.FirstName) %></td>
            <td><%=Html.Encode(obj.LastName) %></td>
            <td><%=Html.Encode(obj.UserName) %></td>
            <td><%=Html.Encode(obj.ProjectID) %></td>
        </tr>
        <% 
        Next
     End If
     %>
</table> 

Masterpage

(code is located in advanced example)

In the classic ASP there is an #include directive is used to create headers, footers that will be reused on multiple pages.
There is a concept of masterpages in the .NET . The masterpage provides

  • a solid view: no header and footer files, just one page, design and see what you get
  • a single point of client resource inclusion: CSS, JavaScripts, etc.
  • easy to switch over

Here we have a Masterpage is included in the default.asp:

%> <!--#include file="views/shared/Site.htmltemplate" --> <%


Masterpage renders header and part of its content and calls the Route() to include dynamic content. After controller/action render a model, the code flow gets back to masterpage. Masterpage then renders the rest of the common content.

Image 7

Here is the tastiest part: in order to switch over the appearance just create another masterpage with new CSS, JavaScripts, menus, etc. included, and then edit only one place: #include reference in the default.asp

Partial view

There is a way to skip rendering Masterpage. If the URL has a variable "partial" , when calling default.asp, the Masterpage is not included but the Route() is called directly.

http://localhost/test_mvc/?controller=PublicationPost&action=List&partial
It can be useful while testing or when updating the page via AJAX.

Server-side master-details.

Master-details are cooked from both sides: from masterform there should be a call to details and a place to display the result. From the details form there should be an ability to get and display a coherent details data.
The details form to display the list of Posts for a given PublicationID:
http://localhost/test_mvc/?controller=PublicationPost&action=ListByPublicationID&PublicationID=1&partial
It is just the same list of Posts, but filtered by PublicationID

The Master form to display the Publication has a call to display the details (a list of Posts) from its view:

<%=Html.RenderControllerAction("PublicationPost","ListByPublicationID", PrepareVariables ( Array("PublicationID="+CStr(Model.Id)))) %>

RenderControllerAction works very much like the Router. It can call and render a controller/action from within another controller/action.

Form handling: User input handling

In contrast with WebForms, the ASP.NET MVC application sends data back to the server in the same way as it does the Classic ASP. It just sends POST or GET forms to the target URL.
We are going to follow this way too:
There is a Controller/Action UserController.Edit that displays Edit form to the user: http://localhost/test_mvc/?controller=User&action=Edit&id=16

    class UserController
        Dim Model
       ...
        public Sub Edit(vars)

            Dim u
            set u = new UserHelper
            set Model = u.SelectById(vars("id"))
            %>   <!--#include file="../views/User/Edit.asp" --> <%
        End Sub
    ... 
    End Class 

After including a view this will produce the HTML:

<form action="?controller=User&action=EditPost" id="EditPost" method="post">
    <input id='id' name='id' type='hidden' value='1' />
    <input id='FirstName' name='FirstName' type='text' value='Bhaskara' />
    <input id='LastName' name='LastName' type='text' value='Ramachandra' />
    <input id='UserName' name='UserName' type='text' value='BRamachandra' />
    <input id='ProjectID' name='ProjectID' type='text' value='1' />
    <button type="submit">Submit</button>
</form> 

When user click the Submit button, the form sends the POST request to the Router for Controller/Action UserController.EditPost.

Router recognizes the Post request, collects the variables and calls the controller/action responsible for the form handling. The Action updates the Data Model.

class UserController
  ...
    public Sub EditPost(args)
        Dim obj, objh
        set objh = new UserHelper
        set obj = objh.SelectById(args("id"))
        obj.FirstName = args("FirstName")
        obj.LastName = args("LastName")
         obj.UserName = args("UserName")
        obj.ProjectID = args("ProjectID")
        objh.Update(obj)

        Response.Redirect("?controller=User&action=list")
    End Sub
...
End Class

Action may redirect the client to the result page after all.

So the only difference between Classic ASP and MVC pattern we are using is an entry point. When Classic ASP usually has a page to receive a Post request, MVC pattern has the Router and a Controller/Action.

For the purpose of this publication I've put pairs of actions within controllers to update the model:

UserController.Edit   + UserController.EditPost
UserController.Create + UserController.CreatePost
UserController.Delete + UserController.DeletePost 

Naturally, isn't it?

Nice URLs

There is only one entry point in the application - the default.asp page. So these URLs work fine:

  • http://localhost/?controller=Home&action=Index
  • http://localhost/default.aspx?controller=Home&action=Index

May it look nice like this?: http://localhost/Home/Index

While the MVC pattern is relatively new, the Classic ASP is really old. There is no real built-in ASP router in the IIS. Thus we need to call The Page (default.asp). However there are couple of tricks to make nice URLs:

  • URL rewrite
  • using URL's like http://localhost/?home/Index or http://localhost/?/home/Index with slight changes to the Router code.
  • using 404 handler (borrowed from simplicity framework)

using 404 handler for nice URLs

Image 8

In order to try the "Nice URLs" in the the IIS 7 follow the steps:

  • In the IIS manager select your web application.
  • Select the "Error pages" feature.

Image 9

  • "Feature settings" should be set to the "Custom error pages"
  • The 404 error should be set to your entry page. Type should be "Execute URL", "local"

Image 10

With these settings the IIS will call your page when the URL in your webapplication points to the non-existing page or folder (the MVC URLS really are the non-existing pages and folders).
Your page (which is working now as 404 handler) can parse and interprete the URL. This "Nice URL" adjustment may require some additional work in the Router, as the original GET parameters are going to be changed.

 

 

Security with MVC pattern

The router file default.asp could be the only ASP file in your IIS folder. If you change the location of the application and update the links to the modules in the default.aps, the entire code may be placed out of IIS folder structure.

Open source and proprietary frameworks for Classic ASP and MVC

There is a number of open source Classic ASP frameworks and proprietary ones that implement MVC:

History

While working on this article I have found the JavaScript in the Classic ASP is really very interesting option.  The JavaScript has been in the Classic ASP since the beginning, a way longer than node.jes or Rhino. It is not as modern as these two, but if you had an experience with IE, you know how to fix the things that are missing or not working in the Classic ASP JavaScript. For more, please read my next article:

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMVC and URL form Pin
mi gramika26-Oct-23 7:21
mi gramika26-Oct-23 7:21 
PraiseSuperb Pin
ian cikal9-Apr-20 7:16
ian cikal9-Apr-20 7:16 
PraiseGreat work Pin
Member 1463851129-Oct-19 11:36
Member 1463851129-Oct-19 11:36 
PraiseGreat article, thank you Pin
she7ata9-Dec-18 11:26
she7ata9-Dec-18 11:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.