Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

9.9KB

DP Jobs Kanban Board

A Kanban Board web application running on IIS (Windows). Uses the RouteKit framework (front-controller pattern) with Keycloak OpenID Connect for authentication. Database is Microsoft Access (.accdb). No Node, npm, or build step — everything is server-side VBScript served by IIS.

Project layout

public/          IIS site root — point IIS here
  Default.asp    Front controller and route table
  web.config     App settings, URL rewrite rules

core/            Framework internals — do not modify
  autoload_core.asp
  mvc.asp
  router.wsc
  lib.*.asp      Core libraries
  helpers.asp    Global utility functions (always available)

app/
  controllers/   Controller classes (one per feature area)
  views/         View partials (folders match controller name)
    shared/      Header, footer, layout partials
  models/        POBOs (plain-old business objects)
  repositories/  Data access classes

db/
  migrations/    Sequential migration scripts
  webdata.accdb  Access database

tests/           Dev-only aspunit test harness (separate IIS app)
scripts/         VBScript code generators

Core libraries (core/lib.*.asp)

File Purpose
lib.Keycloak.asp OpenID Connect / Keycloak auth helper
lib.Routes.asp URL generation helpers (Routes() singleton)
lib.ControllerRegistry.asp Controller whitelist (security)
lib.DAL.asp Database access layer (DAL singleton)
lib.Collections.asp LinkedList_Class and other collection types
lib.Enumerable.asp Collection iteration helpers
lib.Validations.asp Input validation
lib.Flash.asp Flash message helpers
lib.FormCache.asp Form value re-population
lib.HTML.asp HTML rendering helpers
lib.HTML.Security.asp XSS escaping — H() function
lib.Strings.asp String utilities
lib.Automapper.asp Automapper.AutoMap(rs, "POBO_TableName")
lib.json.asp JSON parsing/serialization

Global helper functions (core/helpers.asp)

Always available — never re-implement them.

Function Signature Purpose
H H(s) XSS-safe HTML encode — use on all user data rendered to HTML
GenerateSlug GenerateSlug(title) Converts title to safe URL slug
GetRawJsonFromRequest GetRawJsonFromRequest() Reads raw JSON body from AJAX POST
GetAppSetting GetAppSetting(key) Reads value from web.config appSettings
IIf IIf(condition, trueVal, falseVal) Inline conditional
Destroy Destroy(obj) Safely closes and sets object to Nothing
FormatDateForSql FormatDateForSql(date) Formats VBScript date as SQL datetime string
Active Active(controllerName) Returns "active" if current request maps to that controller

Router — URL parameters

Routes registered in public/Default.asp with :param segments.

router.AddRoute "GET",  "/boards",           "BoardsController", "Index"
router.AddRoute "POST", "/boards",           "BoardsController", "Store"
router.AddRoute "GET",  "/board/:slug",      "BoardsController", "Show"
router.AddRoute "POST", "/cards/:id/move",   "CardsController",  "Move"

Controller action receives URL params as arguments in order:

Public Sub Show(slug) : End Sub
Public Sub Move(id)   : End Sub

URL generation:

Routes.UrlTo "Boards", "Index", Empty
Routes.UrlToWithParams "Boards", "Show", Array("my-board"), Empty
Routes.UrlTo "Boards", "Index", Array("page", 2)

Wiring up a new controller — checklist

  1. Generate: cscript //nologo scripts\generateController.vbs MyController "Index;Show(slug);Store"
  2. Move file to app/controllers/
  3. Register in core/lib.ControllerRegistry.asp: RegisterController "mycontroller"
  4. Include in app/controllers/autoload_controllers.asp: <!--#include file="MyController.asp"-->
  5. Add routes in public/Default.asp
  6. Create views in app/views/MyController/

POBO pattern (app/models/)

Class POBO_boards
    Public Properties  ' array of all column names — required by Automapper
    Private p_id, p_name, p_slug, p_created_at, p_created_by, p_updated_at, p_updated_by

    Private Sub Class_Initialize()
        Properties = Array("id","name","slug","created_at","created_by","updated_at","updated_by")
    End Sub

    Public Property Get PrimaryKey() : PrimaryKey = "id"     : End Property
    Public Property Get TableName()  : TableName  = "boards" : End Property

    Public Property Get id()  : id   = p_id      : End Property
    Public Property Let id(v) : p_id = CDbl(v)   : End Property
    ' ... remaining Get/Let pairs follow same pattern
End Class

Private backing fields use p_ prefix. Properties, PrimaryKey, and TableName are required.

Repository pattern (app/repositories/)

Uses DAL singleton and Automapper. Key methods: FindByID, GetAll, Find, AddNew, Update, Delete. After insert, read identity with SELECT @@IDENTITY AS NewID. Expose as a singleton function (e.g. boards_Repository()).

Controller pattern (app/controllers/)

Class BoardsController_Class
    Private m_useLayout, m_title
    Private Sub Class_Initialize() : m_useLayout = True : m_title = "Boards" : End Sub

    Public Sub Index()
        If Not KeycloakRequireLogin("") Then Exit Sub
    End Sub

    Public Sub Move(id)         ' JSON/AJAX action
        m_useLayout = False
        Response.ContentType = "application/json"
        If Not KeycloakIsLoggedIn() Then
            Response.Write "{""ok"":false,""error"":""Unauthorized""}" : Exit Sub
        End If
    End Sub
End Class

JSON/AJAX actions must set m_useLayout = False and Response.ContentType = "application/json".

Audit columns

Every table must include created_at, created_by, updated_at, updated_by.

' Insert
Dim currentUser : Set currentUser = KeycloakCurrentUser()
Dim currentUsername : currentUsername = ""
If Not currentUser Is Nothing Then currentUsername = currentUser.Item("preferred_username")
model.created_at = Now() : model.created_by = currentUsername
model.updated_at = Now() : model.updated_by = currentUsername
repo.AddNew model

' Update — never touch created_at / created_by
model.updated_at = Now() : model.updated_by = currentUsername
repo.Update model

Authentication (Keycloak)

KeycloakRequireLogin("")          ' Gate full-page actions
KeycloakIsLoggedIn()              ' Use for JSON/AJAX actions
KeycloakCurrentUser()             ' Returns userinfo Dictionary (preferred_username, email, name, sub)
KeycloakHasRealmRole("admin")     ' Role check
KeycloakLogout("")                ' Clear session and redirect

LinkedList_Class — correct traversal

Never use .First, .Last, .m_first, .m_next, or .m_value — they are private internals.

Dim iter : Set iter = myList.Iterator()
Do While iter.HasNext()
    Set item = iter.GetNext()
Loop

Or convert: Dim arr : arr = myList.TO_Array()

AJAX form data — always URLSearchParams, never FormData

Classic ASP Request.Form only parses application/x-www-form-urlencoded. new FormData() sends multipart/form-data which Classic ASP silently ignores.

var params = new URLSearchParams();
Object.keys(data).forEach(function(k) { params.append(k, data[k]); });
fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString() })

Request.BinaryRead / GetRawJsonFromRequest() and Request.Form are mutually exclusive in the same action — never mix them.

View files — Classic ASP scoping rule

View files are SSI-included inside the controller action's Sub scope. Never define Function or Sub inside a view file — VBScript forbids nested procedure definitions (Syntax error 800a03ea). Keep views to pure HTML rendering: <%= %>, H(), simple If/For/Do blocks.

Database DDL — Access/Jet reserved words

Always bracket every identifier. Use migration.ExecuteSQL for all DDL. Never use migration.CreateTable or migration.CreateIndex.

migration.ExecuteSQL "CREATE TABLE [board_columns] ([id] AUTOINCREMENT PRIMARY KEY, [name] VARCHAR(255), [position] INTEGER)"

Common reserved words: COLUMNS, NAME, POSITION, VALUE, DATE, KEY, LEVEL, BY.

Running migrations (32-bit only)

C:\Windows\SysWOW64\cscript.exe //nologo scripts\runMigrations.vbs up

IIS app pool must have enable32BitAppOnWin64="true". Use 32-bit IIS Express: %ProgramFiles(x86)%\IIS Express\iisexpress.exe.

If migration.DB.Execute/Query fails in standalone runner, provide a migration.Connection + ADODB.Command fallback.

Flash messages

Flash().AddError "Something went wrong"
flash.Success = "Saved successfully"

Never use Flash().SetError or Flash().SetSuccess — those methods do not exist (Error 438).

Things to avoid

  • Do not modify files under core/ — framework internals.
  • Do not add controllers without registering in ControllerRegistry.
  • Do not commit real KeycloakClientSecret values.
  • Do not add test routes/pages under public/.
  • Always use H() when rendering user-supplied data (XSS prevention).
  • Never write your own slug generator — use GenerateSlug().
  • Never use Private Const or Public Const inside a VBScript class — use a Private Function returning the value instead.
  • Never run migrations with 64-bit cscript on this machine.
  • Never mix GetRawJsonFromRequest() with Request.Form in the same action.
  • Never call .First/.Last/.m_first/.m_next/.m_value on a LinkedList_Class.
  • Always set all four audit columns on every insert and update.

Requirements

  • Windows Server / Windows with IIS, Classic ASP enabled
  • IIS URL Rewrite module
  • Microsoft Access Database Engine (ACE OLEDB 12.0) — 32-bit
  • Keycloak server (for auth flows)

Powered by TurnKey Linux.