Runtime & designer
This page covers what happens when a wizard runs: how the definition is stored, how a step’s SQL is resolved and executed, the HTTP surface the runner calls, and the visual designer that authors it.
Storage and lifecycle
Section titled “Storage and lifecycle”A wizard definition is a WizardForm row in the Wizard.Forms table — Name, Slug,
Label, Schema, the full FlowXml, and a VersionHash. Submitted runs are WizardFormResponse
rows in Wizard.FormResponses, storing the collected values as JSON in ResponseData.
Wizards are not part of the model-migration pipeline. The build does not copy *.wizard.xml
into bin/models; instead you import the XML through the designer (or POST /save) and it is
persisted to the database. Keep the XML in source control as the reference copy.
Versioning
Section titled “Versioning”Each save that changes the FlowXml mints a fresh 6-char VersionHash and snapshots the XML into
the shared Workflow.FlowVersions history (via IFlowVersionService). Every submitted response is
stamped with the version hash that produced it, so you can tell which definition a response came
from. The designer’s History dropdown lists versions and can load an old one read-only.
The auto-generated response view
Section titled “The auto-generated response view”On save — and again for every wizard at startup via the WizardViewInitializerService background
service — Genie generates a SQL view [Wizard].[vw_{Name}Responses]. It flattens each
response’s JSON into one column per Input field using JSON_VALUE(fr.ResponseData, '$.FieldName'),
joined to the form and the creating/updating users. Attachment fields are projected as
NVARCHAR(MAX). This lets you build an ordinary
table view over submitted responses.
Execution
Section titled “Execution”The runner drives the node graph and calls back to the server whenever a step needs data or SQL.
WizardExecutionService is the backend engine; it locates the relevant block in the FlowXml,
substitutes variables, and runs the SQL.
Variable resolution
Section titled “Variable resolution”Field values arrive keyed as NodeId__FieldName. Before executing any SQL, the service
(PrepareVariablesForSql) builds the substitution scope:
- copies the supplied variables;
- adds a bare
FieldNamealias for eachNodeId__FieldName(so@FieldNameworks); - seeds every declared field/Startup variable that wasn’t supplied as
DBNull(→ SQLNULL); - injects session parameters (
@SessionUserId,@SessionCompanyId,@SessionCartId).
Substitution replaces each @Token with a formatted literal (strings single-quote-escaped and
quoted, booleans as 1/0, null/DBNull as NULL), longest key first so @SessionCompanyId
resolves before @Session, matched on word boundaries so @Email won’t match inside
@EmailAddress. See the substitution rules in Authoring a wizard.
Submission
Section titled “Submission”POST /{id}/submit (WizardExecutionService.SubmitAsync) does the following, in order:
- moves any
Attachmenttemp uploads to permanent storage; - transforms
NodeId__FieldNamevariables to bareFieldNameand writes aWizardFormResponse(JSONResponseData,Statusfrom the Terminal’sSubmissionStatus, the currentVersionHash); - runs
<TerminalSql>if present; - workflow side — mutually exclusive:
- new run → if the Terminal declares a
WorkflowName, starts that workflow viaIWorkflowEngine.StartAsync(workflowName, "WizardFormResponse", responseId, formData); - continue → if the submission carries a
FlowId(edit/resubmit), fires the resubmission transition on the existing instance viaTransitionAsyncand repoints it at the new response.
- new run → if the Terminal declares a
Edit mode and lineage
Section titled “Edit mode and lineage”Loading a prior response for edit is gated: only the latest response in a lineage
(UpdatedTo == null) may be edited; superseded ones return 404. On resubmit, the prior response’s
UpdatedTo is pointed forward to the new row, forming an immutable edit chain. A FlowId requires
an UpdatedForResponseId, must reference an active instance bound to that
WizardFormResponse, or the submit is rejected.
API surface
Section titled “API surface”All endpoints are under /api/v1/genie/wizard and require authentication ([Authorize]).
| Method + route | Purpose |
|---|---|
GET /{key} |
Load a wizard by id, Name, or Slug (the runner’s entry call). |
GET /{id}/response/{responseId} |
Load a prior response for edit mode (latest-in-lineage only). |
POST /save |
Create or update a wizard from { Id?, Name, Slug?, Label, Schema?, FlowXml, Notes? } (upserts by Name). |
GET /{id}/versions |
List saved versions, newest first (History picker). |
GET /{id}/versions/{versionHash} |
Fetch one historical version’s XML (read-only load). |
DELETE /{id} |
Soft-delete a wizard. |
POST /{id}/options/{fieldName} |
Resolve a Select/Radio field’s OptionsSql (body = current variables). |
POST /{id}/field-dataset/{fieldName} |
Searchable dataset for a ModalSelector/Select2 field ({ SearchText?, Variables? }). |
POST /{id}/execute/{nodeId} |
Run an ExecuteSQL action node’s SQL (auto-advance). |
POST /{id}/set-variable/{nodeId} |
Run a SetVariable SQL action and return the scalar. |
POST /{id}/submit |
Submit the wizard: save the response, run terminal SQL, start/continue the workflow. |
Responses use the standard SuccessDataResult<T> envelope. Field names are unique across the
wizard, so the options/dataset/execute endpoints address a field or node without ambiguity.
The runner (UI)
Section titled “The runner (UI)”GenieWizardRunner (driven by the useWizardRuntime hook) executes a wizard for an end user at
/wizard/{slug}. It:
- loads the definition via
GET /{key}, parses theFlowXml, and finds the start node; - walks the graph in an auto-advance driver — Startup, Decision and non-alert Action nodes run
silently (calling
execute/set-variableas needed); the loop stops on an Input step, anAlert, or a Terminal; - renders each Input step with the shared form field controls and layout (GenieField + the form layout engine), pointed at the wizard’s options/dataset endpoints, with a progress bar and Required-field validation;
- shows a read-only summary of every visited step for review, then submits;
- supports edit mode via
responseId(supersede a prior response) andflowId(continue an existing workflow instance), re-walking only the branches the original submission took.
The designer (UI)
Section titled “The designer (UI)”GenieWizardDesigner (route /wizard-designer) is the visual node-graph editor. It provides a
drag-and-drop canvas with the five node types (Startup, Input, Decision, Action, Terminal), port-to-port
link drawing, per-node property panels (fields, variables, action config, terminal handoff), and a
raw-XML code editor — the model round-trips losslessly between the canvas and the <Wizard> XML.
Saving posts to POST /save; the History dropdown surfaces the version list. Saved wizards are
listed by the registered table view at /table/wizard-definitions.