Demo App One Plus One Software Logo

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 on DemoApp).
  • 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 CreateAsync on the entity (the way Settings does).

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 on App that returns ReturnDetails.
  • 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 through ButtonClicked.
  • 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 with noindex.
  • A new screen that should be indexed: add it to Seo.PublicMethods and give it a PageMeta in PageMeta.Screens. An admin screen goes into AdminMethods.
  • Stored metadata and computed metadata both go through a single interface — IPageMeta. A page generated dynamically builds PageMeta.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.xml and robots.txt are 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/; the hreflang tags are derived automatically from the shared Identify.
  • In production: fill in SiteBaseUrl in 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.