Skip to content

Latest commit

 

History

History
324 lines (249 loc) · 10.4 KB

File metadata and controls

324 lines (249 loc) · 10.4 KB

ASPPY App Rules

Read this before building any new app in www.

Start every new app from www_starter.

  • Do not begin from a blank www folder when creating a new app
  • Copy the starter into www, then build from that structure
  • The rules below still apply to the final app in www

Hard Rules

  • Build inside www
  • Do not modify ASPPY\*.py
  • Do not modify www_starter\*.*
  • Use ASPPY only
  • Do not add IIS requirements
  • Run the dev server on port 8080
  • After editing any included .asp file, restart the ASPPY server process to clear the in-memory ASP compilation cache
python -m ASPPY.server 0.0.0.0 8080 www

Use One App Model Only

Pick one model and stay consistent.

MVC app

  • Use www/default.asp as the router
  • Use www/index.asp to include default.asp
  • Use Request.Path for routing
  • Keep app pages in ASP/VBScript
  • Keep server-side Sub and Function blocks inside ASP script tags; do not close %> before executable VBScript definitions

MVC Structure Rules

For MVC apps, www/default.asp is only the front controller and router.

Required separation:

  • www/default.asp may normalize routes, parse route params, choose a handler, and return 404s
  • Route handlers/controllers should live in www/asp/
  • Database access should live in dedicated model/data files in www/asp/
  • Page rendering should live in dedicated view/template files or included ASP view files
  • Create www/asp/views/ for every MVC app that renders pages
  • Controllers should prepare data and choose a view; they should not contain full-page HTML built from long Response.Write sequences
  • Do not build full pages with large Response.Write blocks inside www/default.asp
  • Do not put page-specific SQL statements directly in www/default.asp
  • Do not create tables, run migrations, or perform other app initialization in www/default.asp on every request
  • default.asp should dispatch; controllers should coordinate; models should read/write data; views should render output

Rendering ownership:

  • Pick one layout ownership model and keep it consistent
  • Either default.asp owns RenderHeader() and RenderFooter(), or each controller action owns them
  • Do not call RenderFooter() in both default.asp and the controller for the same request

Bad MVC example:

  • one default.asp file that routes requests, runs SQL, handles POST bodies, and prints all HTML

Good MVC example:

  • default.asp routes /contacts/1/edit to a contacts controller
  • asp/controllers/contacts.asp handles request logic
  • asp/models/contacts.asp reads and writes the database
  • asp/views/contact_edit.asp renders the form

Minimal pattern:

www/
  default.asp
  index.asp
  asp/
    controllers/
      contacts.asp
    models/
      contacts.asp
    views/
      contacts_list.asp
      contact_edit.asp
' default.asp
If RouteIs("/contacts") Then
    Call ContactsIndex()
End If
' asp/controllers/contacts.asp
Sub ContactsIndex()
    contacts = ContactAll()
    <!--#include file="../views/contacts_list.asp"-->
End Sub
' asp/models/contacts.asp
<!--#include file="../db.asp"-->

Static app

  • Use plain .html, .css, .js
  • Do not mix it with ASP MVC routing

Do not mix both models in one app.

Bad:

  • public site in index.html
  • posts saved by ASP
  • public content loaded from JavaScript localStorage

That creates two different apps that do not share the same data.

Mandatory MVC Rules

For every new ASPPY MVC app:

  • www/default.asp is the front controller
  • extensionless routes like /posts/hello must be handled by www/default.asp
  • unknown routes must return Response.Status = "404 Not Found"
  • missing .asp files must stay real 404s
  • do not bypass MVC by linking directly to internal /asp/*.asp files unless truly needed
  • verify every include path from the perspective of the file that contains the include
  • verify that layout rendering is owned by one place only

Path Rules

There are 3 different path types. Do not mix them.

  1. Route path
    Example: /posts/hello

  2. Include path
    Example: db.asp or ../asp/db.asp

  3. Server.MapPath() path
    Example: data/app.db

Very Important MapPath() Rule

Server.MapPath("...") is relative to the ASP file that is executing.

That means the same path string can point to different folders in different files.

Example:

  • in www/default.asp, Server.MapPath("data/posts.txt") points to www/data/posts.txt
  • in www/asp/save_post.asp, Server.MapPath("data/posts.txt") points to www/asp/data/posts.txt

This is a common source of broken apps.

So:

  • never guess where Server.MapPath() points
  • never assume the same relative path means the same file everywhere
  • use one consistent strategy for shared files

Safe Path Rules

  • Always use relative include paths
  • Include paths are relative to the file that contains the include
  • Always use Server.MapPath("...") for files and databases
  • Never use a leading slash in Server.MapPath
  • Never hardcode physical paths
  • Never pass route URLs into Server.MapPath()

Common include examples by file location:

  • from www/default.asp to www/asp/helpers.asp: <!--#include file="asp/helpers.asp"-->
  • from www/asp/controllers/contacts.asp to www/asp/models/contacts.asp: <!--#include file="../models/contacts.asp"-->
  • from www/asp/models/contacts.asp to www/asp/db.asp: <!--#include file="../db.asp"-->
  • from www/asp/views/contact_edit.asp to www/asp/layout.asp: <!--#include file="../layout.asp"-->

Correct:

<!--#include file="asp/db.asp"-->
<!--#include file="../asp/db.asp"-->
<!--#include file="../db.asp"-->
dbPath = Server.MapPath("data/app.db")

Wrong:

<!--#include file="db.asp"-->
<!-- inside asp/models/*.asp when the real file is one folder up -->
<!--#include file="db.asp"-->
dbPath = Server.MapPath("/data/app.db")
dbPath = "C:\ASPPY\www\data\app.db"
dbPath = Server.MapPath("/posts")

Files And Folders

Use this structure:

www/
  default.asp
  index.asp
  asp/
  assets/
  data/
  uploads/

Rules:

  • CSS, JS, and app-used images go in www/assets
  • user uploads go in www/uploads
  • shared ASP helpers go in www/asp
  • shared data files go in www/data

Do Not Make These Mistakes

  • Do not redirect index.asp to /
  • Do not create both index.html and MVC default.asp as competing homepages
  • Do not mix ASP server-side storage with browser localStorage for the same feature
  • Do not save data in one place and read it from another
  • Do not use IIf(...) for nullable SQL values or request logic
  • Do not write output before Response.Redirect
  • Do not concatenate raw request IDs directly into SQL

Development Rules

  • Read browser error messages fully
  • Fix current errors before moving on
  • Handle POST logic before writing page output
  • If a page redirects, redirect before writing output
  • Validate numeric IDs with IsNumeric() and CInt()
  • HTML-encode user output with Server.HTMLEncode()
  • Avoid On Error Resume Next in routing or page setup; fix the first real error directly
  • For dynamic routes, prefer splitting path segments over manual string-length math when extracting IDs or actions

Dynamic route example:

Dim routeParts, contactId, actionName
routeParts = Split(Trim(CurrentRoute(), "/"), "/")

' /contacts/12/edit -> contacts, 12, edit
If UBound(routeParts) = 2 Then
    contactId = routeParts(1)
    actionName = routeParts(2)
End If

CRITICAL: Subscript Out Of Range in Route Arrays

VBScript And does not short-circuit. Every operand is evaluated regardless of the result of the first.

This means the following code crashes when the route has fewer segments than expected:

' BROKEN: parts(2) is evaluated even when UBound(parts) is 0
If UBound(parts) = 2 And parts(0) = "contacts" And IsNumeric(parts(1)) And parts(2) = "edit" Then

The error you will see is Subscript out of range, and it will point to a line that looks correct. The real cause is VBScript evaluating parts(1) or parts(2) on a shorter array because And checked all operands.

Workaround: use nested If blocks so array indices are only accessed after the length is confirmed.

Dim parts, partCount
parts = RouteParts()
partCount = UBound(parts)

' 2-segment route: /contacts/1
If partCount = 1 Then
    If parts(0) = "contacts" Then
        If IsNumeric(parts(1)) Then
            Call ContactsShow(parts(1))
        End If
    End If
End If

' 3-segment route: /contacts/1/edit
If partCount = 2 Then
    If parts(0) = "contacts" Then
        If IsNumeric(parts(1)) Then
            Select Case parts(2)
                Case "edit"
                    Call ContactsEdit(parts(1))
                Case "delete"
                    Call ContactsDelete(parts(1))
            End Select
        End If
    End If
End If

The outer If partCount = N guarantees parts(0) through parts(N) exist before any inner line touches them. Never combine segment count checks and segment value checks in a single And expression.

Initialization rule:

  • Do not run schema creation or app setup in www/default.asp
  • Put setup in a dedicated script, admin-only route, or clearly isolated bootstrap helper that is not triggered on every request
  • If setup must run conditionally, make that condition explicit and limited

Frontend Framework Rule

  • The developer must explicitly choose one frontend framework before building UI
  • Allowed choices: Bootstrap 5 (latest version) or Tailwind
  • Do not leave styling undecided or mix both frameworks in one app
  • If the user does not specify a framework, choose Bootstrap 5 and state that choice clearly while building

Final Checklist

Before finishing, verify:

  • the app uses one model only: MVC or static
  • www/default.asp handles MVC routes
  • www/default.asp only dispatches routes and does not contain page-specific SQL or large HTML rendering blocks
  • www/index.asp does not create a redirect loop
  • extensionless routes work through Request.Path
  • dynamic routes are verified with real examples like /contacts/1, /contacts/1/edit, and /contacts/1/delete
  • missing .asp files return real 404
  • includes use correct relative paths
  • every include target exists relative to the including file
  • layout rendering is owned by one place only
  • shared files in www/data are read and written from the same real location
  • assets are in www/assets
  • uploads are in www/uploads
  • pages load without ASPPY runtime errors