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, SYSDATETIMEOFFSET());
]]>
</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 = SYSDATETIMEOFFSET(), UpdatedById = @SessionUserId
WHERE Id = @Id AND IsDeleted = 0 AND CompanyId = @SessionCompanyId;
]]>
</EditSql>
<DeleteSql>
<![CDATA[
UPDATE [Inventory].[Brands]
SET IsDeleted = 1, DeletedAt = SYSDATETIMEOFFSET(), 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:

"Genie": {
"Migration": {
"MigrationExecution": "Forced",
"ModelsPath": "models"
}
}

No skips migration, Yes applies only changed files (hash check), and Forced re-applies every file regardless of hash. ModelsPath says where the model files are — relative to the app output (as here, where the sample copies ../models/** into bin/.../models) or an absolute path. See Model migration.

Point the browser (or the navbar) at /object/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="/object/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 MigrationExecution = Yes (or Forced)
Add a stored proc / SQL view *.sql run with MigrationExecution = Yes
Change navigation *.navbar.xml run with MigrationExecution = Yes

Next: the complete authoring reference → Model Authoring.

Ready to ship? Deployment shows how to serve the built UI from the API host (UseGenieSpa — one deployable, zero CORS preflights) or behind a reverse proxy.