Skip to content

Quickstart

Get the reference app running, then add your own entity + view. About 10 minutes.

  • .NET SDK matching global.json (net10.0, SDK 10.0.300+).
  • Node 22+ and npm (for the React UI).
  • SQL Server reachable at Server=. (the local default instance, integrated security).

No Redis required — the Inventory sample runs its cache in-memory. Adjust the connection string in sample/Inventory/api/appsettings.json for your environment:

"ConnectionStrings": {
"SqlServer": "Server=.;Database=InventorySample;Integrated Security=true;TrustServerCertificate=True;MultipleActiveResultSets=True"
}
Terminal window
dotnet build src/Genie.slnx # .NET libraries + source generator
cd src/ui/genie-engine-ui && npm ci && npm run build # the React package
Terminal window
# backend → http://localhost:5184
# (creates the InventorySample DB, applies the migration, and seeds sample data on first run)
dotnet run --project sample/Inventory/api
# frontend → http://localhost:5183 (proxies /api, /files, /hubs to the backend)
cd sample/Inventory/ui && npm install && npm run dev

Open http://localhost:5183: you get the login page, then the permission-filtered navbar and the engine’s table/form views.

The sample’s low-code models live in sample/Inventory/models/:

models/
entities/ *.entity.xml Category, Product, Supplier, Warehouse, StockMovement,
PurchaseOrder, PurchaseOrderLine (→ EF tables)
views/ *.view.xml grids + forms for each object
sql/ *.sql functions, stored procedures, triggers, reporting views
navbar/ *.navbar.xml the navigation tree
wizards/ *.wizard.xml multi-step wizards
workflows/ *.workflow.xml workflow definitions

They are wired in sample/Inventory/api/Inventory.Sample.Api.csproj: *.entity.xml are fed to the source generator as AdditionalFiles, and every model file is copied to the build output so the runtime model migration can find it.

Create sample/Inventory/models/entities/Brand.entity.xml:

<?xml version="1.0" encoding="utf-8"?>
<Schema>
<Entity Name="Brand" PluralName="Brands" SchemaName="Inventory"
Description="Manufacturer brand a product belongs to">
<Fields>
<String Name="Name" Label="Brand" Required="true" Searchable="true" Limit="200" />
<String Name="Website" Label="Website" Required="false" Limit="256" />
<Boolean Name="IsActive" Label="Active" Required="true" DefaultValueIfNull="1" />
</Fields>
</Entity>
</Schema>

The generator picks it up on the next build (it matches the existing AdditionalFiles glob) and emits a Brand entity, its EF configuration, and a DbSet<Brand> Brands on InventoryContext.

The generated entity classes are part of InventoryContext, so a normal migration creates the table. Stop the running host first (EF rebuilds the project, which can’t overwrite a locked, running binary):

Terminal window
dotnet ef migrations add AddBrand \
--project sample/Inventory/api \
--startup-project sample/Inventory/api \
--context InventoryContext \
--output-dir Migrations/InventoryMigrations

Run the host again; the migration is applied at boot (Database.Migrate()).

Create sample/Inventory/models/views/Brands.view.xml — the root element is <Table>, and whether it behaves as a grid, a form, or a read-only view is inferred from which SQL blocks it declares:

<?xml version="1.0" encoding="utf-8"?>
<Table Name="Brands" Slug="brands" Label="Brands" Icon="fa fa-tag">
<Sql PrimaryKey="Id" SortBy="Name" SortDirection="ASC" PageSize="20">
<![CDATA[
SELECT b.Id, b.Name, b.Website, b.IsActive
FROM [Inventory].[Brands] b
WHERE b.IsDeleted = 0
AND b.CompanyId = @SessionCompanyId
]]>
</Sql>
<Columns>
<Column Name="Id" Hide="true" />
<Column Name="Name" Label="Brand" Search="true" StyleClasses="fw-bold" />
<Column Name="Website" />
<Column Name="IsActive" Label="Active" />
</Columns>
<EditorFields>
<Text Name="Name" Label="Brand" Required="true" MaxLength="200" />
<Text Name="Website" Label="Website" MaxLength="256" Placeholder="https://…" />
<Boolean Name="IsActive" Label="Active" DisplayType="Switch" />
</EditorFields>
<Layout>
<Section Label="Brand">
<Item Name="Name" Width="8" />
<Item Name="IsActive" Width="4" />
<Item Name="Website" Width="12" />
</Section>
</Layout>
<InsertSql>
<![CDATA[
INSERT INTO [Inventory].[Brands] (Name, Website, IsActive, IsDeleted, CompanyId, CreatedById, CreatedAt)
VALUES (@Name, @Website,
CASE WHEN UPPER(ISNULL(@IsActive,'')) IN ('TRUE','1','YES','Y','ON') THEN 1 ELSE 0 END,
0, @SessionCompanyId, @SessionUserId, GETDATE());
]]>
</InsertSql>
<EditSql>
<![CDATA[
UPDATE [Inventory].[Brands]
SET Name = @Name, Website = @Website,
IsActive = CASE WHEN UPPER(ISNULL(@IsActive,'')) IN ('TRUE','1','YES','Y','ON') THEN 1 ELSE 0 END,
UpdatedAt = GETDATE(), UpdatedById = @SessionUserId
WHERE Id = @Id AND IsDeleted = 0 AND CompanyId = @SessionCompanyId;
]]>
</EditSql>
<DeleteSql>
<![CDATA[
UPDATE [Inventory].[Brands]
SET IsDeleted = 1, DeletedAt = GETDATE(), DeletedById = @SessionUserId
WHERE Id = @Id AND CompanyId = @SessionCompanyId;
]]>
</DeleteSql>
</Table>

Views are loaded into the engine’s ViewStore by the model migration at startup. The Inventory sample already enables it in sample/Inventory/api/appsettings.json:

"Startup": { "ExecuteMigration": "Forced" }

No skips migration, Yes applies only changed files (hash check), and Forced re-applies every file regardless of hash. How the engine locates the models directory (and how the sample copies models to the build output) is covered in Integration.

Point the browser (or the navbar) at /table/brands — the UI route uses the view’s kebab-case Slug. To add it to the navigation, drop an <Item> into sample/Inventory/models/navbar/inventory.navbar.xml:

<Item Name="brands" Label="Brands" Icon="fa fa-tag" Route="/table/brands" />

To make records findable from the global header search (enable it with createGenieApp({ modules: { headerSearch: true } })), mark a field Searchable="true" and add a <Search> block to the entity — see Model Authoring.


Want to… Edit Then
Add/lengthen a column *.entity.xml rebuild → dotnet ef migrations add → run
Change a grid/form *.view.xml run with ExecuteMigration = Yes (or Forced)
Add a stored proc / SQL view *.sql run with ExecuteMigration = Yes
Change navigation *.navbar.xml run with ExecuteMigration = Yes

Next: the complete authoring reference → Model Authoring.