For SyteLine / CSI developers

From playground to production: typed C# for SyteLine IDOs

Stop guessing IDO property names. Generate a strongly-typed C# SDK from your own SyteLine tenant.

var items = await SlItems.Query()
    .Select(x => x.Item, x => x.Description, x => x.UnitCost)
    .Filter("UnitCost > 100 AND (Description LIKE 'FG%' OR Stocked = 1)")
    .OrderBy("UnitCost DESC, Item")
    .Take(50)
    .ToListAsync();   // typed rows; UnitCost is decimal?, not a string

639 properties on SLItems alone, IntelliSense on every one of them, including your ue_ customizations.

The pain of raw CSI REST development

Every CSI developer has lived these:

  • Property names guessed from form fields or SQL tables. A typo returns silent nulls, not an error.
  • Stored-procedure invokes take a positional string array. One misplaced parameter and the SP runs with the wrong values, with no compile error.
  • Every value comes back as a JSON string. Dates arrive as "20260518 17:05:15.070".
  • Token exchange, session lifetime, paging bookmarks, retries: all hand-rolled.
Before: raw ION REST (abridged)
// 1. OAuth token dance (client id/secret + service account keys)... ~30 lines elsewhere
// 2. Build the load URL by hand; property names are guesses
var url = $"{baseUrl}/load/SLItems?properties=Item,Description,UnitCost" +
          "&filter=UnitCost%20%3E%20100&orderBy=UnitCost%20DESC,Item&recordCap=50";
var resp = await http.GetAsync(url);
var json = await resp.Content.ReadAsStringAsync();
// 3. Everything is a string; parse each field yourself and hope the name was right
foreach (var row in JsonDocument.Parse(json).RootElement.GetProperty("Items").EnumerateArray())
{
    var costText = row.GetProperty("UnitCost").GetString();          // "40.57200000"
    var cost = decimal.Parse(costText!, CultureInfo.InvariantCulture);
    // dates arrive as "20260518 17:05:15.070" and need a custom parse too
}
// 4. MoreRowsExist? Build the bookmark continuation loop yourself.

The same read, with the generated SDK, is the six lines at the top of this page.

What csigen does

.ionapi + configyour machine, metadata only
csigen generate--ido SLItems
Live metadata + IDO XMLtypes + methods
Generated C# projectrows, methods, query builder
Your apptyped, compile-safe
  • Run one command against your own tenant with your own .ionapi. Credentials never leave your machine; the tool reads metadata only.
  • It emits a compilable C# project: typed row records, typed method wrappers from the IDO definition, a fluent query builder, and JSON converters for CSI's string-typed wire format.
  • Because it reads your tenant live, customizations (ue_ fields) come along with exact types.

The generated surface, by example

1. Typed projection query

var rows = await SlItems.Query()
    .Select(x => x.Item, x => x.Description, x => x.UnitCost)
    .OrderBy("Item").Take(3)
    .ToListAsync(client);
FG-1001   Finished widget, blue     40.572
FG-1002   Finished widget, red      180.320
FG-1003   Finished widget, green    180.320

2. Auto-paging stream

Bookmark handling disappears.

await foreach (var row in SlItems.Query()
    .Select(x => x.Item)
    .OrderBy("Item")
    .Take(500)                       // page size; bookmarks handled for you
    .ToStreamAsync(client))
{
    Process(row.Item);
}

3. Typed method call with named parameters

The positional-roulette killer. Inputs are named, typed, and placed in the IDO's parameter order for you.

InvokeResult result = await SlItems.CanUpdateCostsSpAsync(
    pItem: "FG-1001",
    pCostType: "S",
    pCostMethod: "S",
    pPMTCode: "",
    pJob: "",
    pSuffix: "",
    whse: "MAIN");
// Under the hood: positional slots in IDO Sequence order, output slots as placeholders.

4. Ambient client, set once

CsiGatewayDefaults.Client = client;                 // Program.cs, once
var pick = await SlItems.Query().Select(x => x.Item).Take(10).ToListAsync();  // no client arg

5. Regeneration-safe extensions

Hand-written members live in a sibling file and survive a csigen re-run.

// Yours, in a sibling file; never overwritten by a csigen re-run.
public sealed partial record SlItemsRow
{
    public string DisplayName => $"{Item} - {Description}";
}

The speed is in what disappears

csigen does not type faster. It deletes whole categories of write-run-debug loops.

TaskRaw ION RESTWith SDK + csigen
Find a property on a 639-column IDOSearch docs/SQL, guess, re-runIntelliSense; a typo is a compile error
Read a decimal or dateParse strings by hand, culture bugsTyped properties; converters handle CSI formats
Call an SP with 12 parametersHand-build a positional string arrayNamed, typed arguments in order
Page through 10k rowsManual bookmark loopawait foreach auto-paging
Tenant adds a ue_ columnHand-edit models, hopeRe-run csigen; the typed property appears
Auth / session / retriesHand-rolledSDK-managed session and resilience

SLItems alone carries 639 properties and 88 methods with 545 parameters on a customized tenant. Nobody memorizes that. Your compiler can.

Your customizations come along

Generated from a live tenant, the row record includes your ue_ fields as first-class, correctly typed members. This is the part no stock, pre-built SDK can do, and it is why csigen runs at your site instead of shipping generic DLLs.

// Generated from a live tenant: ue_ fields are first-class, correctly typed members.
DateTime? added = row.ue_CustomDate;
string?   code  = row.ue_CustomCode;
IntelliSense on SlItemsRowscreenshot pending
csigen generate outputterminal capture pending

Getting started

Today

The CsiGateway.Client SDK is packaged and shipping. Explore any IDO live in the playground, then write typed queries against it.

Early access csigen

Generate a typed SDK from your own tenant (generate --ido YourIdo). We are onboarding early users now.

On the roadmap: relationship navigation, multi-IDO runs, typed method outputs, and dotnet-tool packaging.