Skip to main content

Commands

Create snippets of code that work across the Jetveo application. Allows for direct data manipulation inside application database.

Testing is also available. See Test in the upper right of the App Builder screen. See below for more information.

Creating a new a command​

General​

  • Name - name of a command, shown in a list of commands
  • Code identifier - code identifier of the command used internally and when logging application errors
  • Model - the command's input. Every command has one; see Command model below. A command that works on an entity record declares a reference to that entity in its model, a command that works on a selection declares an Entity Collection reference. There are no separate entity, batch or child command types any more - the model says what the command needs, and the place that invokes it fills it in.
  • Result type - result of the command. Allows for creating Data grids or Documents from command results.

UI​

  • Default title for action button - default label used on a new Action button and on a Custom command page
  • Default icon for action button - default icon used in a label of a new Action button and on a Custom command page

Security​

  • Execution condition - security condition that needs to return true for the command to be executed

Command model​

Go to the "Model" tab and define the model's Attributes and References. You can use Inherited aspects. The model is what the command body receives as its first parameter:

(model, db, ctx) =>
{
var order = model.Order; // a Direct reference: one entity record
var selected = model.Selection; // an Entity Collection reference: the records selected in a grid
...
}

An Entity Collection (ICollection<T>) is filled either with records - from a multiselect in the command's page, or from a mapping that returns a list of entities - or with a query, which is how a Data grid hands over its selection. A collection filled with a query reads nothing until the body reads it: use model.Selection.Query() to query or iterate it in the database, because enumerating the collection itself loads the records and is limited to 1000 of them. See Entity collection limit.

Values reach the model in two ways:

  • Parameter mapping at the place that invokes the command - a page action, an action button, a Data grid command or a business event. Each mapping is an expression evaluated on the server when the button is pressed; see Parameter mapping.
  • The command's own page, for values the app user types in. Go to the "View" tab and lay out the model's fields like any other view. An action of type Open command window opens this page with the mapped values prefilled; Execute command skips the page and runs the command with the mapped values only.

Parameter mapping​

A mapping assigns one model member an expression. The expression's parameters depend on where the command is invoked:

Invoked fromExpressionNotes
Page action, action button(model, db, ctx) => ...model is the page's entity (or the standalone page's model)
Data grid command(row, model, db, ctx) => ...row is the grid row, model the page's entity
Data grid batch command(selection, model, db, ctx) => ...selection is the selected rows as ICollection<T>, backed by a query
Business event(changes, db, ctx) => ...changes describes what the triggering save changed; changes.Entity is the entity

Typical mappings: (model, db, ctx) => model hands the page's entity to a reference of the same type; (row, model, db, ctx) => row hands the grid row; (row, model, db, ctx) => model hands the parent from the page to a child command; (selection, model, db, ctx) => selection fills an Entity Collection reference with the selection's query, and (selection, model, db, ctx) => selection.Query().Where(...) with a narrower one. A mapping to a Direct reference travels as the record's id and is resolved again in the command's own database session, so the body always works with a live entity.

The expression has to return what the member holds: the attribute's type, the referenced entity or codebook entry for a single reference, and any IEnumerable of the target - a list, the selection or a query - for a collection reference. A value of another type is reported as an error in the expression editor and when the package is validated.

A command is offered to a page or a grid when its model declares a reference the place can fill - a reference to the page's entity, to the grid's entity, or an Entity Collection of it.

Commands on a Create page​

On a Create page the entity does not exist in the database yet. A command action there still receives it: the mapping (model, db, ctx) => model hands the command a draft - a detached copy of what the form holds, under the id the form works with. Inside the command body:

  • the draft is only reachable through the model; db.OrderSet.Find(model.Order.Id) returns nothing;
  • what the body changes on the draft comes back into the form once the command succeeds (see Reporting changes back);
  • saving inside the command does not write the draft. The command may still write other records, or insert the draft itself with db.OrderSet.Add(model.Order) - it is then saved under the same id, exactly as a record built in code;
  • immediate handlers that run when a record is created run once it is really added, not while it is a draft.

This replaces the former immediate command adapter. Existing create-page commands that filled the form or saved the record themselves keep working; the difference is that the command sees the form's entity as a draft rather than a copy.

Reporting changes back to the form​

Whenever a command changes an entity it was handed through its model - a stored record or a draft - the change is reported back to the page that ran it, by entity id and property, and the page updates every field that shows that entity. For a stored record, only what the command saved is reported; for a draft, everything the body changed on it. Collections are reported as the list of ids they hold afterwards. Nothing has to be configured for this.

Local functions​

If you want to use a function exclusively for your command, you can declare the function within the scope of the command. The following is a simple example:

(exampleEntity, db, ctx) =>
{
int sum = 0;

int foo()
{
App.Log.Info("Calling foo");
return 2;
}

for (int i = 0; i < 5; i++)
{
sum += foo();
}
}

Using a command​

A command can be attached to:

  • Entity pages and their tabs (page actions - Execute command or Open command window)
  • Views (action buttons)
  • List pages and Data grids (commands and batch commands)
  • Business events (on created, updated, deleted; on a child added, updated or removed; on an attribute change)
  • Application menu items (the command's own page)
  • API endpoints, for 3rd-party integration
  • Scheduled tasks

What decides whether a command is offered at a place is its model: a grid offers commands whose model can take its rows, a page offers commands whose model can take its entity, and a command with no entity reference can go anywhere. Each place fills the model through its parameter mapping.

Command results​

Command results let you show or save result of a command.

Report result​

Returns a table of rows and columns rendered as a report in the Jetveo UI, or consumed directly by REST API callers as an array of objects.

In the following example we create a Data grid that lists application users and their emails:

The most convenient entry points are the two static factory methods:

// From a collection — reflects public properties as columns
return ReportResult.FromEnumerable(orders);

// From a single object — same reflection, one row
return ReportResult.FromSingle(summary);

For full control, use the direct constructor:

var columns = new[]
{
new ReportResult.Column("Product", ReportResult.ColumnType.Text),
new ReportResult.Column("Quantity", ReportResult.ColumnType.Number),
};
var rows = items.Select(i => new ReportResult.Row(i.Product, i.Quantity));
return new ReportResult(columns, rows, message: "Stock levels");

FromEnumerable and FromSingle support string, Guid, bool, int, long, float, and decimal properties. Properties of other types are silently excluded from the output.

Report results expire after 60 minutes. After that the report is no longer available.

Document result​

Returns a document that the Jetveo UI presents as a download link, inline preview, or automatic download depending on the configured options.

In the following example, we use Razor + HTML template to render a new html page and then create a byte stream out of it to allow app users to save it:

var htmlTable = App.Templates.HtmlReport.Render(templateModel);
var tableBytes = Encoding.UTF8.GetBytes(htmlTable);

return new DocumentResult("report.html", tableBytes);

You can specify Options to control the behavior of the user interface:

  • ShowDownloadLink: Displays a file download link if the file name is not empty. The default value is TRUE.
  • ShowPreview: Enables a document preview in the results window. This option is applicable only for the following file extensions: .html, .htm, .pdf, .jpg, .png, .svg. The default value is TRUE.
  • AutoDownload: Automatically downloads the document and closes the command results window. The default value is FALSE.
  • AllowPublicAccess: Enables anonymous users to download the document. The default value is FALSE.
  • DownloadAsAttachment: Forces the document to be downloaded instead of displayed inline by the browser. The default value is FALSE.

In the following example, the application will begin downloading the document immediately and will close the command window.

Constructors:

// Ad-hoc byte array — no storage lookup required
return new DocumentResult("export.xlsx", excelBytes);

// Ad-hoc string content — encoded as UTF-8
return new DocumentResult("report.html", "<h1>Report</h1>");

// Persistent document already in document storage
return new DocumentResult(document); // IDocument
return new DocumentResult(documentRevision); // IDocumentRevision

Pass a DocumentResult.Options record to control how the document is presented in the UI:

return new DocumentResult("report.pdf", pdfBytes, options: new DocumentResult.Options(
AutoDownload: true, // download immediately and close the result window (default: false)
ShowPreview: false, // suppress inline preview (default: true)
ShowDownloadLink: true, // show a download link (default: true)
DownloadAsAttachment: true, // force download instead of inline browser display (default: false)
AllowPublicAccess: false // allow unauthenticated download (default: false, use with caution)
));

Preview is supported for .html, .htm, .pdf, .jpg, .png, and .svg files.

Redirect result​

Specialized type of CommandResult that enables users to specify a URL to which they want to redirect, along with the target behavior of the redirect. This class is particularly useful for scenarios where navigation control is required within an application.

Parameters:

  • Url: A string representing the URL to which the user will be redirected.
  • OpenTarget: An enumeration value that specifies how the URL should be opened. The possible values are:
    • SameTab: Opens the URL in the current tab, replacing the existing content.
    • NewTab: Opens the URL in a new browser tab.
    • Modal: Opens the URL in a modal dialog within the application.
string dashboardUrl = "https://example.com/dashboard";
return new RedirectResult(dashboardUrl, RedirectResult.Target.NewTab);

Data result​

DataResult is a command result type for returning data payloads from Jetveo commands exposed via the REST Command API. When a command returns a DataResult, the framework serializes it directly into the HTTP response — no extra wiring needed.

Use it when a command needs to return structured data rather than a simple success/failure acknowledgement:

(model, db, ctx) =>
{
var report = db.Orders.Where(o => o.UserId == ctx.User.Id).ToList();
return DataResult.Json(report);
}

Six response formats are available out of the box: enveloped JSON following the Jetveo command protocol (CommandJson), plain JSON (Json), pre-serialized JSON pass-through (Json(string)), XML (Xml), plain text and other text formats (Text), and raw binary for file downloads (Data). All formats except the legacy constructor use System.Text.Json.

See full reference in Command API documentation.

Queued vs Asynchronous commands​

Queued commands​

The default mode of command execution is queued. This means when the command is invoked, it is placed into the command queue, and executes only after all preceding commands in the queue. This is usually not a problem, as the command queue is usually empty.

Asynchronous commands​

Asynchronous commands start executing immediately upon invocation. You can think of it as a second thread, separate from the main command queue. This can be particularly useful if you expect the command execution to last for a long duration, and when the following commands do not depend on the changes made by the current one.

The platform limits the number of concurrent asynchronous commands to 64.

There is no way to lock shared data in an asynchronous commands, so be aware that race conditions can occur.

Invoking Commands in code​

Each command has an object accessible in the code: App.Commands.ExampleCommand. You can invoke a command by calling the InvokeAsync(ExampleCommandModel model) method on this object, while passing in the command model.

This method immediately invokes the command, and proceeds to the next line. Notably this means the code does not wait for the command to finish executing, and cannot use the command's result. When dealing with synchronous commands, the invoked command begins executing only after the invoker command has finished.

Special case: Deadlock​

When an app uses its own command API within a command, the command API must be asynchronous. Otherwise this will result in a deadlock.