Read this before building any new app in www.
Start every new app from www_starter.
- Do not begin from a blank
wwwfolder 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
- 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 wwwPick one model and stay consistent.
- Use
www/default.aspas the router - Use
www/index.aspto includedefault.asp - Use
Request.Pathfor routing - Keep app pages in ASP/VBScript
- Keep server-side
SubandFunctionblocks inside ASP script tags; do not close%>before executable VBScript definitions
For MVC apps, www/default.asp is only the front controller and router.
Required separation:
www/default.aspmay normalize routes, parse route params, choose a handler, and return404s- 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.Writesequences - Do not build full pages with large
Response.Writeblocks insidewww/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.aspon every request default.aspshould 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.aspownsRenderHeader()andRenderFooter(), or each controller action owns them - Do not call
RenderFooter()in bothdefault.aspand the controller for the same request
Bad MVC example:
- one
default.aspfile that routes requests, runs SQL, handlesPOSTbodies, and prints all HTML
Good MVC example:
default.asproutes/contacts/1/editto a contacts controllerasp/controllers/contacts.asphandles request logicasp/models/contacts.aspreads and writes the databaseasp/views/contact_edit.asprenders 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"-->- 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.
For every new ASPPY MVC app:
www/default.aspis the front controller- extensionless routes like
/posts/hellomust be handled bywww/default.asp - unknown routes must return
Response.Status = "404 Not Found" - missing
.aspfiles must stay real404s - do not bypass MVC by linking directly to internal
/asp/*.aspfiles 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
There are 3 different path types. Do not mix them.
-
Route path
Example:/posts/hello -
Include path
Example:db.aspor../asp/db.asp -
Server.MapPath()path
Example:data/app.db
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 towww/data/posts.txt - in
www/asp/save_post.asp,Server.MapPath("data/posts.txt")points towww/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
- 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.asptowww/asp/helpers.asp:<!--#include file="asp/helpers.asp"--> - from
www/asp/controllers/contacts.asptowww/asp/models/contacts.asp:<!--#include file="../models/contacts.asp"--> - from
www/asp/models/contacts.asptowww/asp/db.asp:<!--#include file="../db.asp"--> - from
www/asp/views/contact_edit.asptowww/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")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 redirect
index.aspto/ - Do not create both
index.htmland MVCdefault.aspas competing homepages - Do not mix ASP server-side storage with browser
localStoragefor 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
- Read browser error messages fully
- Fix current errors before moving on
- Handle
POSTlogic before writing page output - If a page redirects, redirect before writing output
- Validate numeric IDs with
IsNumeric()andCInt() - HTML-encode user output with
Server.HTMLEncode() - Avoid
On Error Resume Nextin 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 IfVBScript 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" ThenThe 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 IfThe 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
- 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
Before finishing, verify:
- the app uses one model only: MVC or static
www/default.asphandles MVC routeswww/default.asponly dispatches routes and does not contain page-specific SQL or large HTML rendering blockswww/index.aspdoes 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
.aspfiles return real404 - 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/dataare read and written from the same real location - assets are in
www/assets - uploads are in
www/uploads - pages load without ASPPY runtime errors