How to build a new application from the template
The canonical guide page — for humans and for Claude. DemoAppCore is the source code of the method: every new One Plus One application starts out as a copy of it.
1 · Getting started
- Copy the solution structure:
DataAccessLayer,BusinessLogicLayer,UI, and a Web application (based onDemoApp). - DAL and UI are copied bit for bit — they are the canon and must never be modified per project. Need a general fix? Make it here in the template first.
- Project name, namespace, logo and title — replaced in the application only.
- A unique development port per application (launchSettings) — never reuse the same port across projects.
- There is an automated skill for this:
new-app-from-demoappcore.
2 · A new entity (BLL)
public class Invoice : Record
{
[Column(Size = 300)]
public override string Description { get; set; }
[Column] public double Amount { get; set; }
[Column] public DateTime Issued { get; set; }
[Column] public bool IsPaid { get; set; }
}
- The table is created and updated by itself in
SetupAsync— no SQL to write and no migrations. - Table names are singular.
DateTime.MinValue= no date. - An entity that is not a real table (demo or display only):
[Table(Ignore = true)]. - Seed data: override
CreateAsyncon the entity (the waySettingsdoes).
3 · An admin screen
// DemoApp/Tables/InvoicesTable.cs
public class InvoicesTable : TableMakerEx<Invoice>
{
public InvoicesTable()
{
this["Description"].HeaderText = "Description";
this["Amount"].HeaderText = "Amount";
this["IsPaid"].HeaderText = "Paid";
this["IsPaid"].Filterable = true;
}
}
// Pages/Invoices.html
<h1>Invoices</h1>
<div id="InvoicesTable" class="table"></div>
// Code/App.cs
public ReturnDetails Invoices() => AdminPage("Invoices");
// Pages/Main.html — a button in the menu
<input type="button" id="Invoices" value="Invoices" />
That is a complete screen: grid, sorting, filtering, edit form, export. No JS and no SQL.
4 · Pages that are not tables
- A page = an HTML file under
wwwroot/Pages+ a method onAppthat returnsReturnDetails. - A button invokes the method that matches its
id— no onclick, no new endpoint. - Parameters from the page arrive in
Data(a dictionary); the server's answer can push back html, popUp, script or the contents of lists. - Files under
Pages/are blocked from direct browser access — they are served only throughButtonClicked. - Application state (the properties of
App) survives between calls — encrypted and compressed on the client.
5 · Content, SEO and multiple languages
- A content page is a record (
ContentPage), not a file: friendly URL, H1, its own title, description, share image and structured data. Edited in the "Content pages" screen. - Server-side rendering for every public page (
Code/Seo.cs): the very first response carries the content and the complete<head>. The admin console stays a SPA and is served withnoindex. - A new screen that should be indexed: add it to
Seo.PublicMethodsand give it aPageMetainPageMeta.Screens. An admin screen goes intoAdminMethods. - Stored metadata and computed metadata both go through a single interface —
IPageMeta. A page generated dynamically buildsPageMeta.For("/…").Titled(…)at run time. - Every change of address requires a row in "Redirects" (
PageRedirect), otherwise every external link breaks silently. The old numeric address (/Page@41) stays resolvable forever. sitemap.xmlandrobots.txtare built from the data on their own; the chrome is translated through tokens ({text}⇐Word), and long copy is written as one record per language.- The slug is always in English, lowercase and hyphenated. The default language lives at the root and the other languages under
/en/; thehreflangtags are derived automatically from the sharedIdentify. - In production: fill in
SiteBaseUrlin the settings — the canonical URL and the sitemap both rely on it.
6 · Non-negotiable coding rules
- reuse-first: before writing anything, check what already exists in
Util, the DAL and the UI. - Asynchronous only: no synchronous DB calls; async flows all the way up to the controller.
- Targeted updates:
UpdatePropertiesAsync("Col"), never a full save. - Styling in CSS only, sizes in
rem; a change to CSS/JS ⇐ bump?v=. - Settings live in the DB, not in appsettings; the development DB = localhost/SQLite.
- Fields that are not Hebrew (email, phone) —
LTR. - Background jobs must be resumable.
7 · Before you call it done
- A clean build — no warnings left open.
- code-review on the diff + a second opinion (Gemini).
- A real smoke test in the browser — the whole flow, not just a successful compile.
- commit to SVN with a meaningful message; .md files go in too.
- Deployment: a portable, self-contained folder; to PROD only with explicit approval.