# HCI-SunBird SunBird is HCI's lightweight content management system for hand-built ASP.NET Core Razor sites. It ships as NuGet packages that a host site adds. Developers and agents build each page by hand, with its own design and CSS. The editable parts of a page are named **slots**: a title, a block of rich text, a photo. A signed-in editor turns on edit mode, changes slot values over a live preview of the page, and saves a draft or publishes. SunBird is deliberately small: - Editors change content only. Layout, design, headers and footers stay in code. - Pages are built in code. Editors can add pages only from templates a developer built, such as news stories, under the pages the developer allows (see [Let editors add pages](how-to/create-pages-from-templates.md)). - A typical site has one editor, so there are no approval workflows or roles. - Each client site is its own app with its own database. > [!NOTE] > SunBird is in development. Pages marked **Coming** describe agreed behaviour that is not built yet. ## Packages | Package | Purpose | |---|---| | `HCI.SunBird` | The core: slot tag helpers, edit mode, sign-in and account management. | | `HCI.SunBird.Data.SqlServer` | SQL Server storage and migrations: the default, for production on Azure and LocalDB in development. | | `HCI.SunBird.Data.Sqlite` | SQLite storage and migrations, for running a site with no database server. | ## Where to start - [Getting started](getting-started.md): add SunBird to a site. - [Pages and slots](concepts/pages-and-slots.md): the model every other page builds on. - [How-to guides](how-to/index.md): step-by-step recipes for common tasks. - [API reference](api/index.md): every public type and member. ## For agents The whole of these docs is available as plain Markdown at [`/llms-full.txt`](/llms-full.txt), with an index at [`/llms.txt`](/llms.txt). --- # Getting started > [!NOTE] > All four steps work today: pages show their published content, or else their markup defaults, and signed-in editors can [edit text, rich text and images on the live page](how-to/edit-a-page.md). ## 1. Add the packages Add the core package and a storage package from the HCI NuGet feed. SQL Server is the default; for a site with no database server, use `HCI.SunBird.Data.Sqlite` and `UseSqlite(...)` instead: ```shell dotnet add package HCI.SunBird dotnet add package HCI.SunBird.Data.SqlServer ``` ## 2. Register SunBird In `Program.cs`: ```csharp using HCI.SunBird.Data.SqlServer; using HCI.SunBird.Web; builder.Services.AddSendGridEmail(builder.Configuration); // SunBird emails invites and password resets builder.Services.AddAzureBlobStorage(builder.Configuration); // SunBird keeps uploaded images here builder.AddSunBird().UseSqlServer(builder.Configuration.GetConnectionString("SunBird")!); var app = builder.Build(); app.MapSunBird(); // applies SunBird's database migrations on start ``` See [Store content in SQL Server](how-to/store-content-in-sql-server.md) or [Store content in SQLite](how-to/store-content-in-sqlite.md) for the connection string, what SunBird needs from the database, and the tables it creates. Set `SunBird:SiteUrl` and `SunBird:Email:From` too; see [Sign in and manage editors](how-to/sign-in-and-manage-editors.md) for email, and [Store images](how-to/store-images.md) for the file store. ## 3. Make a page editable Give the page a key with `[SunBirdPage]` on its PageModel (`using HCI.SunBird.Web;`): ```csharp [SunBirdPage("home")] public class IndexModel : PageModel { } ``` Register the tag helpers once, in `Pages/_ViewImports.cshtml`: ```cshtml @addTagHelper *, HCI.SunBird ``` Then mark the editable parts of the page. The content inside each tag is the default, shown until an editor publishes a value: ```cshtml

Build calm, fast websites

A short introduction.

``` See [Make a page editable](how-to/make-a-page-editable.md) for partials, images and what the tags render, and [Pages and slots](concepts/pages-and-slots.md) for the rules on keys and defaults. ## 4. Create the first admin Set a setup code as the `SunBird:SetupCode` setting (an App Service setting, or user secrets locally; never in source control), then open `/sunbird/setup` and create the first admin account. See [Sign in and manage editors](how-to/sign-in-and-manage-editors.md). --- # Pages and slots ## Pages A page is a Razor page built in code, or a page an editor added from a template built in code (see [Let editors add pages](../how-to/create-pages-from-templates.md)). SunBird identifies it by a **page key**. A code page sets its key with the `[SunBirdPage]` attribute on its PageModel; a created page gets a generated one: ```csharp [SunBirdPage("about")] public class AboutModel : PageModel { } ``` The key is an opaque identifier. It is not the URL, so a page's URL can change without losing its content. Keys are 1 to 100 characters of ASCII letters, digits, `-`, `_` and `.`. SunBird stores nothing for a page until an editor first saves it. ## Slots A slot is one editable piece of a page. There are six kinds: | Tag helper | Kind | Holds | |---|---|---| | `` | Text | Plain text, HTML-encoded when rendered. | | `` | Rich text | HTML, written by SunBird when saved from the editor's structured document, using only `p`, `h2` to `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `strong`, `em`, `s`, `sup`, `sub`, `a href` (with `target="_blank" rel="noopener"` for a new-tab link) and `br`. | | `` | Image | A reference to an image in the [media library](media-library.md), plus its alt text. | | `` | Link | A link's text, and where it goes: a page of the site, or an address. | | `` | File | A download link's text, and a reference to a PDF in the [media library](media-library.md). | | `` | Table | A table's column headings, and its sections of rows, all plain text. | | `` | Icon | The name of a Material Symbols icon, such as `home`. | Each slot has a `key` that is unique on its page, with the same character rules as page keys. A slot belongs to the page being served, not to the file it is written in. Slots can be in a page's partials, and a partial used on several pages gives each page its own slots. To use a partial more than once on the same page, wrap each use in ``, which prefixes the keys inside it (`top.hero-title`). See [Make a page editable](../how-to/make-a-page-editable.md). For a list of the same thing, such as gallery pictures or testimonials, `` repeats its content once per entry, and editors add, remove and reorder the entries. Each entry's slots are stored under the repeater key and the entry number (`gallery.2.caption`). See [Repeat parts of a page](../how-to/repeat-parts-of-a-page.md). Content shown on many pages, such as testimonials or a footer, can be a **fragment**: a partial view with `[SunBirdFragment("testimonials")]`, shown with ``. Its slots belong to the fragment, not the page, so editors change them once for every page, and it has its own history. See [Share content between pages](../how-to/share-content-between-pages.md). ## Default content lives in the markup The content inside a slot tag is its default: ```cshtml

Build calm, fast websites

``` The page shows the default until an editor publishes a value. The developer or agent who builds the page decides the placeholder. ## Rules - Public page views never write to the database. A page shows the published value of each slot, or else its markup default. - Slots belong in pages and the partials they render. A slot in a layout, or in a section a layout renders, is always logged. In Development, and on staging with `ShowSlotErrors` on, the page shows a clear error in place of the slot. On the live site the slot shows its markup default. See [Settings](../settings.md). - Headers, footers and other layout content are managed in code, except for fragments (``), which may be in layouts. - Removing a slot from a page's markup is safe. Its saved values stay in the page's history and are simply not rendered. --- # Drafts and history Every save creates or replaces a **version** of the page. A version is a full snapshot of all the page's saved slot values and its [SEO settings](seo.md), so any point in time can be viewed or restored as it was. ## Saving In edit mode, the editor changes slots and then chooses: - **Save as draft**: keeps the changes unpublished. Visitors still see the published version. - **Save and publish**: makes the changes live. - **Schedule...**: makes the changes live at a set date and time. See [Schedule publishing](../how-to/schedule-publishing.md). A page has at most one draft. Saving a draft again replaces it. Slots the editor did not change are carried forward from the draft, or else from the live version. A waiting schedule is never changed by editing. ## Publishing and version numbers Publishing or scheduling freezes the draft as the next numbered version. A draft has no number until then. Versions are numbered 1, 2, 3 in order. A cancelled schedule keeps its number, so it shows as a gap among the versions that went live. What visitors see is the **live** version: the one that went live most recently, counting a scheduled version from its release time. ## History and rollback The **History** button on the toolbar opens a rail listing the page's versions. It is there while viewing a page, not in edit mode. The editor can preview any version and publish it. Publishing an older version does not go back in time. It adds a new published version with that version's content, and records which version it was copied from. Any draft is left as it is. **History only grows.** No published version is ever changed or removed. Cancelling a schedule marks the version cancelled and keeps it. > [!NOTE] > Saving drafts, publishing, and previewing and restoring versions from the History rail all work today; see [Edit a page](../how-to/edit-a-page.md). --- # Media library Images and PDF documents are stored once, in the site's media library. An image slot stores only a reference to a library image plus its own alt text, and a file slot (``) a reference to a document plus its link text, so any number of pages, fragments and versions can use the same file without copying it. ## Choosing an image Clicking an image slot in edit mode opens the media rail on the right. The editor picks an existing image, or uploads a new one, which is added to the library and selected. The page shows the new image straight away; it is kept when the editor chooses **Save as draft** or **Save and publish**, like any other change. The page's share image, in **Page settings**, is chosen from the same rail. See [SEO page settings](seo.md). ## Choosing a document Clicking a file slot in edit mode opens the same rail, listing the library's documents instead of its images. The editor picks one or uploads a PDF, and Save as draft or Save and publish keeps it. Each picker lists only its own kind, and a save that puts a document in an image slot, or an image in a file slot, is refused. ## Uploads - Accepted types: JPEG, PNG, WebP and GIF images, and PDF documents. SVG is refused. The type is checked from the file's contents, not its extension, and the image picker accepts only images, the document picker only PDFs. - The size limit is 10 MB by default, and configurable with `SunBird:Media:MaxUploadMegabytes`. - SunBird does not resize images. Upload them at the size the site needs. - Uploading a file that is already in the library reuses the existing image instead of storing a copy. ## Alt text Alt text belongs to the slot, not the image, because the same photo can mean different things on different pages. The editor changes it in the media rail. An empty alt text marks a decorative image. Until an image is chosen for a slot, the slot keeps its built-in image and alt text from the markup. ## Deleting An image or document can be deleted when no page's or fragment's current published version or draft uses it. If older versions use it, the editor is warned that those versions will lose the image, and must confirm. A deleted image's file is removed, but its library record stays. Previewing an old version that used it shows the slot's markup default, outlined as deleted. Restoring that version publishes it without the image, so the slot shows its markup default (and a deleted share image is left out). A file slot whose document was deleted falls back to its markup `href` and text in the same way. ## Storage Files are kept through the site's HCI.FileStorage `IFileStorageService`, such as Azure Blob Storage, and served by SunBird at `/sunbird/media/{id}`, or for a document `/sunbird/media/{id}/{file-name}`. See [Store images](../how-to/store-images.md). --- # SEO page settings > [!NOTE] > The title, meta description and share image all work today. Each page has SEO settings that editors can change: - **Title**: the page's ``, also used as its Open Graph title when it is shared. - **Description**: the meta description, also its Open Graph description. - **Share image**: the image social networks show when the page is shared, chosen from the [media library](media-library.md). `<sb-seo />` renders it as `og:image`, with an absolute address built from `SunBird:SiteUrl`. A page has no share image until an editor chooses one. They are part of each version, so they are drafted, published, previewed and restored together with the page's slots. ## Defaults in code The page builder sets the defaults on the page key attribute. Saved values override them, and an editor who clears a field goes back to the default: ```csharp [SunBirdPage("home", Title = "Acme Plumbing", Description = "Plumbers in York since 1990.")] public class IndexModel : PageModel { } ``` The title can be at most 200 characters, and the description 500. ## Rendering Put `<sb-seo />` in the `<head>` of the site's layout, **instead of** a `<title>` tag: ```cshtml <head> <meta charset="utf-8"> <sb-seo /> <link rel="stylesheet" href="~/css/site.css"> </head> ``` On a SunBird page it renders, for example: ```html <title>Acme Plumbing ``` The canonical link and `og:url` are the page's own address. They are built from `SunBird:SiteUrl`, the same setting the links in emails use, never from the request's host. The path is the page's route, so `/about`, `/About` and `/about?utm_source=newsletter` all give the same canonical address. A page created from a template uses its own lowercase address, such as `/news/opening-day`. When the page has a share image, `og:image` is built the same way and `twitter:card` is `summary_large_image`, so X shows a large card; otherwise it is `summary`. It leaves out a title or description tag whose value is empty. On a page without `[SunBirdPage]` it renders nothing, so give such pages their own `` (for example in a `@section`). Everything is HTML-encoded. `<sb-seo />` is the one SunBird tag that belongs in a layout; slots do not. For a signed-in editor in edit mode it shows the draft's settings, and when previewing an older version it shows that version's. ## Editing In edit mode, the **Page settings** button opens the settings in the rail on the right. Each field shows the default from code as its placeholder. Under each field is its length, or the default's when the field is empty, against where Google cuts it off in search results: about 60 characters for the title and 160 for the description. It turns red when the text is longer. The settings are saved with the page, by **Save as draft** or **Save and publish**. ## Not page settings `noindex` and canonical URLs are not editable settings. `<sb-seo />` always renders the page's own canonical address. When a page needs `noindex`, the developer or agent puts it in the page itself. There is no meta keywords tag: search engines ignore it. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/concepts/staging.html --> # Staging mode SunBird sites are released to an Azure App Service staging slot, checked, then swapped into production. The staging slot uses the **live database**, so SunBird runs there in staging mode. ## Turning it on Set this app setting on the staging slot, marked as a deployment slot setting so it stays with the slot when swapping: ``` SunBird__Mode = Staging ``` The setting is `Mode` in the `SunBird` section, and its values are `Live` (the default) and `Staging`. Any other value, such as a misspelling, stops the site starting, so a typo cannot leave publishing switched on. See [Settings](../settings.md). ## What staging mode does - Edit mode shows a banner on the toolbar saying this is staging, on live content. - Editors can save and view drafts, preview any version, and read the history. - Anything that changes what visitors see is turned off, for pages and for shared fragments: - **Save and publish**, **Schedule...** and **Publish this version** are not offered. - **Cancel schedule** is not offered, since it changes what goes live. - **Unpublish** and **Delete page** are not offered for added pages. - The matching endpoints refuse the request with `403` and a message saying this is the staging site, so a request made by hand cannot publish either. - Adding a page from a template still works. The new page is a draft, and stays hidden until it is published on the live site. - Agents working through the [MCP server](../how-to/let-agents-edit-content.md) can add pages and save drafts, but are not offered the tools that publish, schedule, roll back, unpublish or delete, and are refused if they call them. ## Releasing new content 1. Deploy the release to the staging slot. 2. In staging, build the content for new or changed pages as drafts. 3. Swap staging into production. 4. In production, open each draft and publish it. To release it at a set time instead, [schedule it](../how-to/schedule-publishing.md) there. ## Rule for developers Staging (new code) and production (old code) share the database during a release, so every database migration must work with both. Migrations only add (expand); anything removed is removed in a later release (contract). --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/index.html --> # How-to guides Step-by-step recipes for building and running SunBird sites. Each guide is added when the feature it describes is released. New to SunBird? Start with [Getting started](../getting-started.md). - [Make a page editable](make-a-page-editable.md) - [Edit a page](edit-a-page.md) - [Repeat parts of a page](repeat-parts-of-a-page.md) - [Share content between pages](share-content-between-pages.md) - [Schedule publishing](schedule-publishing.md) - [Let editors add pages](create-pages-from-templates.md) - [Sign in and manage editors](sign-in-and-manage-editors.md) - [Let agents edit content over MCP](let-agents-edit-content.md) - [Store images](store-images.md) - [Store content in SQL Server](store-content-in-sql-server.md) - [Store content in SQLite](store-content-in-sqlite.md) --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/make-a-page-editable.html --> # Make a page editable A page becomes editable in three steps: give it a page key, register the tag helpers, and wrap each editable part in a slot tag with its default content inside. The examples on this page come from the sample site in the SunBird repository (`samples/HCI.SunBird.SampleSite`), which is SunBird's own home page built with SunBird. This guide assumes SunBird is already registered with a database. See [Getting started](../getting-started.md). ## 1. Give the page a key Put `[SunBirdPage]` on the page's PageModel: ```csharp [SunBirdPage("home", Title = "SunBird: a calm CMS for hand-built sites", Description = "SunBird lets editors change the words and pictures on a hand-built Razor site, on the live page, without touching its design.")] public sealed class IndexModel : PageModel { } ``` The key is an opaque identifier, not the URL. It is how SunBird finds the page's content, so changing the page's route or file name keeps its content, and changing the key loses it. Keys are 1 to 100 characters of ASCII letters, digits, `-`, `_` and `.`, and each page's key must be unique on the site. A page with no PageModel can set the key in the view instead: ```cshtml @page @attribute [HCI.SunBird.Web.SunBirdPage("contact")] ``` ## 2. Register the tag helpers Add one line to `Pages/_ViewImports.cshtml`: ```cshtml @addTagHelper *, HCI.SunBird ``` ## 3. Mark the editable parts Wrap each editable part in a slot tag. Each slot needs a `key` that is unique on its page. The content inside the tag is the default, which is shown until an editor publishes a value: ```cshtml <h1><sb-text key="hero-title">Build calm, fast websites</sb-text></h1> <sb-html key="hero-intro"> <p>We design and build websites for small organisations.</p> </sb-html> <sb-image key="hero-image" src="/img/hero.jpg" alt="Our office in York" class="hero-image" width="1200" height="600" loading="lazy" /> ``` ### What each tag renders The slot tags themselves never reach the browser. | Tag | With a published value | Otherwise | |---|---|---| | `<sb-text>` | The text, HTML-encoded. An editor typing `<b>` gets the characters `<b>`, not bold text. | The content inside the tag. | | `<sb-html>` | The HTML SunBird wrote when it was saved. | The content inside the tag. | | `<sb-image>` | An `<img>` with the published image and its alt text. | An `<img>` with the tag's own `src` and `alt`. | | `<sb-link>` | An `<a>` with the published text and address. | An `<a>` with the tag's own `href` and content. | | `<sb-file>` | An `<a>` to the published document, with the published text. | An `<a>` with the tag's own `href` and content. | | `<sb-icon>` | An inline `<svg>` of the published icon. | An inline `<svg>` of the icon in the tag's own `name`. | | `<sb-table>` | A `<table>` with the published columns, sections and rows. | A `<table>` with the tag's own content. | Put the slot inside the element that styles it (`<h1><sb-text …></h1>`), not around it. `<sb-image>` keeps every attribute other than `key`, `src` and `alt` (for example `class`, `width`, `height`, `loading`), so the page's CSS and layout stay the same whatever the editor picks. Public page views only read. Nothing is written to the database until an editor saves. Each page's published content is read once and then cached in memory until the page is next published. Every copy of the site sharing the database, such as staging and production, checks every 5 seconds whether anything was published elsewhere, so a change shows on all of them within seconds. ## Links and buttons `<sb-link>` makes a link an editor can change, such as a call-to-action button. The content inside is its default text and `href` its default address: ```cshtml <sb-link key="cta" href="/contact" class="button">Get a quote</sb-link> ``` It renders an `<a>` that keeps every attribute other than `key` and `href`, so it can be styled like a button. Editors change its words on the page and choose where it goes in edit mode: - **A page of the site**: any page with `[SunBirdPage]` and an address without parameters, or a page editors added. The page is saved by its key, so the link follows it if its address changes. While the page is unpublished or deleted, the link uses its `href` from the markup. - **An address**: one that starts with `https://`, `http://`, `mailto:`, `tel:` or `/` (a page of this site). Anything else, such as `javascript:`, is refused. Links work inside `<sb-scope>` and `<sb-repeater>` like any slot, for example a button in each entry of a list. ## Downloads `<sb-file>` makes a download link, such as a form or a leaflet, whose document editors choose. The content inside is its default text and `href` its default address: ```cshtml <sb-file key="referral-form" href="/docs/referral-form.pdf" class="download" show-size>Referral form</sb-file> ``` It renders an `<a>` that keeps every attribute other than `key`, `href` and `show-size`. Editors change its words on the page; clicking it in edit mode opens the media rail, where they pick a PDF from the [media library](../concepts/media-library.md) or upload one. Once a document is chosen the link goes to `/sunbird/media/{id}/{file-name}`, which opens the PDF in the visitor's browser. `show-size` adds the document's type and size after the text, in a span you can style: ```html <a href="/sunbird/media/…/Referral%20form.pdf" class="download">Referral form <span class="sb-file-size">(PDF, 147 KB)</span></a> ``` Until an editor chooses a document, the link uses its `href` and content from the markup, with no size. Only PDF documents can be chosen. ## Tables `<sb-table>` makes a table whose cells editors change, and whose rows, columns and sections they add, remove and move, such as a price list or opening hours. The tag becomes the `<table>`, and the rows inside it are the default: ```cshtml <sb-table key="prices" class="price-list"> <thead> <tr><th scope="col">Treatment</th><th scope="col">Description</th><th scope="col">Price</th></tr> </thead> <tbody> <tr><th scope="rowgroup" colspan="3">Check-ups</th></tr> <tr><th scope="row">Examination</th><td>A full check of teeth and gums</td><td>£25</td></tr> <tr><th scope="row">X-ray</th><td>Small, one or two</td><td>£15</td></tr> </tbody> <tbody> <tr><th scope="rowgroup" colspan="3">Hygiene</th></tr> <tr><th scope="row">Scale and polish</th><td>Half an hour with the hygienist</td><td>£60</td></tr> </tbody> </sb-table> ``` Each `<tbody>` is a **section**. A section's first row is its heading when it is one cell across the table (with `scope="rowgroup"`, or a `colspan` of every column); a section can also have no heading, so a plain table is one `<tbody>`. The `<thead>` row holds the column headings. A published table is always written in exactly this shape, and nothing else, so style it by structure rather than by classes. Write the default in the same shape, as above, and one set of rules styles both: | Part | Rendered as | Select it with | |---|---|---| | Column headings | `<thead><tr><th scope="col">` | `thead th` | | A section | `<tbody>` | `tbody` | | A section's heading | `<tr><th scope="rowgroup" colspan="3">` | `th[scope="rowgroup"]` | | A row's first cell | `<th scope="row">` | `th[scope="row"]` | | The other cells | `<td>` | `td`, `td:last-child` for a price column | The table keeps every attribute other than `key`, such as `class`. SunBird adds no classes, styles or `<caption>`, and leaves out the `<thead>` when every column heading is blank. Classes on rows and cells in the default are not kept once an editor saves the table. Cells hold plain text, like `<sb-text>`. A table has 1 to 12 columns, at least one row in every section and at most 200 rows in all, and a cell holds at most 500 characters; a save that breaks these is refused. Tables work inside `<sb-scope>`, `<sb-repeater>` and fragments like any slot. ## Icons `<sb-icon>` shows an icon editors can change, such as the icon on a service or feature card. `name` is the default icon, by its name in Google's [Material Symbols](https://fonts.google.com/icons) (Outlined), such as `home`, `call` or `local_shipping`: ```cshtml <sb-icon key="icon" name="home" class="feature__icon" /> ``` It renders the icon as an inline `<svg>` that keeps every attribute other than `key` and `name`: ```html <svg viewBox="0 -960 960 960" width="1em" height="1em" fill="currentColor" class="feature__icon" aria-hidden="true"><path d="…"/></svg> ``` - **Size and colour** come from CSS, as for text: the icon is `1em` square in `currentColor`, so `font-size` and `color` on its parent work, or set `width`, `height` and `fill` on its own class. - **Screen readers** skip it, as most icons sit beside words that say the same. If the icon is the only thing that says what something is, give the tag an `aria-label`; it then renders with `role="img"`. - **Nothing else loads.** SunBird carries the whole Material Symbols set (about 4,000 icons, weight 400, unfilled, 24px optical size) inside its DLL and writes only the one icon into the page: no icon font, no script and no request to Google. The icons are Google's, under the Apache License 2.0, which SunBird ships at `/_content/HCI.SunBird/icons/material-symbols-LICENSE.txt`. Clicking an icon in edit mode opens the icon rail, where editors search the library by name or by tag (`house` finds `home`) and click the one they want. It is saved with the page like any slot. A `name` that is not in the library is a slot error. If a saved icon is ever dropped from a later version of the library, the page shows its `name` instead. Icons work inside `<sb-scope>`, `<sb-repeater>` and shared fragments like any slot. ## Titles and descriptions Set the page's default title and meta description on the attribute, and put `<sb-seo />` in the layout's `<head>` instead of a `<title>`. Editors can then change both in edit mode: ```csharp [SunBirdPage("contact", Title = "Contact us", Description = "Call, email or visit our York office.")] ``` See [SEO page settings](../concepts/seo.md). ## Slots in partials Slots can be in the page's own view and in any partial the page renders. A slot belongs to the **page being served**, not to the file it is written in. If a partial with `hero-title` in it is used on three pages, each page has its own `hero-title`, edited separately. ## Using a partial more than once on a page Slot keys must be unique on a page, so a partial used twice would give two slots the same key. Wrap each use in `<sb-scope>`, which puts its key in front of the keys inside it. The sample site's "How it works" section uses one partial for its three steps: ```cshtml <ol class="steps"> <li><sb-scope key="step-1"><partial name="_Step" model="1" /></sb-scope></li> <li><sb-scope key="step-2"><partial name="_Step" model="2" /></sb-scope></li> <li><sb-scope key="step-3"><partial name="_Step" model="3" /></sb-scope></li> </ol> ``` The partial uses plain keys: ```cshtml @model int @* One step of "How it works". The page wraps each use in <sb-scope>, so the keys below are per step. *@ @{ var (title, body) = Model switch { 1 => ("Mark the editable parts", "Wrap a heading, a paragraph or a picture in a slot tag, with your placeholder inside it."), 2 => ("Give the page a key", "Put [SunBirdPage(\"home\")] on the PageModel. The key is not the URL, so pages can move without losing content."), _ => ("Hand it over", "Your client signs in, edits on the live page, and publishes when they are happy. Every version is kept."), }; } <article class="step"> <span class="step__number" aria-hidden="true">@Model</span> <h3><sb-text key="title">@title</sb-text></h3> <sb-html key="body"><p>@body</p></sb-html> </article> ``` The slots are stored as `step-1.title`, `step-1.body`, `step-2.title` and so on. Scopes can be nested (`outer.inner.title`). The full key, including every scope, must still be at most 100 characters. Like the slot tags, `<sb-scope>` renders only its content. ## Where slots cannot go Slots belong to pages, so they are refused in: - **Layouts**, and partials rendered by a layout. Header, footer and other layout content are managed in code, except a [fragment](share-content-between-pages.md) (`<sb-fragment>`), whose slots may be anywhere. - **Sections** (`@section`) that a layout renders. These are rendered while the layout runs, so SunBird cannot tell them apart from the layout. Put the slot in the page body instead. ## Mistakes and how they show up These are mistakes in the page's code: - a slot on a page without `[SunBirdPage]` - a slot without a `key`, or with an invalid key - two slots with the same key on one page - a slot in a layout or a section, outside a fragment Every one is logged as an error. What the page shows depends on the `SunBird:ShowSlotErrors` setting: - **On**: the faulty slot is replaced by a red box that says what is wrong and how to fix it, for example *SunBird slot error: `<sb-text key="title">` is used more than once on page 'home'…*. The rest of the page renders as normal, so you can see every problem on a page at once. - **Off**: the slot shows its markup default, so a mistake never spoils a live page. It is on in Development and off everywhere else unless you set it. Turn it on for staging, to check a release visually before it goes live, and leave it off on the live site. See [Settings](../settings.md). --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/edit-a-page.html --> # Edit a page Editors change a page's content on the live page itself. There is no separate admin screen for content. ## For editors 1. Sign in at `/sunbird/login`, then open any page of the site. A small **SunBird** toolbar appears in the bottom-right corner. 2. Choose **Edit this page**. The editable parts of the page are outlined. If the page has an unpublished draft, you see the draft. 3. Click an outlined text and type. Rich text (paragraphs, headings, lists with one level of nesting, quotes, dividing lines, bold, italic, strikethrough, superscript, subscript and links, which can open in a new tab or go to a phone number or a place on the page) has its own toolbar above it, with the usual shortcuts (Ctrl+B, Ctrl+I, Ctrl+K for a link). Click an outlined image to change it (see [Images](#images)). Changed slots are outlined in amber. 4. Choose: - **Save as draft** to keep the changes unpublished. Visitors still see the published page, and you can come back and carry on later. - **Save and publish** to make the changes live. The page reloads as visitors see it. 5. **Exit edit mode** returns to the normal view. If you have unsaved changes, the browser asks before you leave. If someone else saved the page after you opened it, SunBird refuses your save rather than overwrite their changes: copy any text you want to keep, reload the page, and make your changes again. ## Images In edit mode, click an image (or tab to it and press Enter) to open the media rail on the right: - Pick an image from the library, or choose **Upload an image** to add a JPEG, PNG, WebP or GIF from your computer. It is added to the library and picked. The page shows it straight away. - Write the **Alt text**: a short description for people who cannot see the image. Leave it empty if the image is only decoration. - **Delete** removes an image from the library. SunBird will not delete an image that a page shows now or has in a draft. If older versions used it, it asks first: those versions will show the page's built-in image instead. The new image is kept when you choose **Save as draft** or **Save and publish**. See [Media library](../concepts/media-library.md). ## Repeated entries Parts of a page that are a list, such as a gallery, have a dashed frame round them in edit mode. **Add entry**, on the frame's bottom edge, adds one at the end, and a small bar just above each entry's top-right corner has buttons to move it **Up** or **Down**, or **Remove** it. Each change is saved as a draft straight away, with any other unsaved changes, and the page reloads. See [Repeat parts of a page](repeat-parts-of-a-page.md). ## Shared fragments Some content is shared by several pages, such as testimonials. In edit mode it has a solid frame with a bar along its bottom edge, and it cannot be changed from the page. Choose **Edit fragment** on the bar to edit it: only the fragment is editable, and its bar has **Save as draft**, **Save and publish** and **Exit edit mode**, which save it for every page that shows it. The toolbar along the bottom is for the page. **Fragment history** lists its versions, to preview or publish again. Restoring an earlier version of a page does not change its fragments. See [Share content between pages](share-content-between-pages.md). ## Links and buttons In edit mode, links that editors can change, such as buttons, are outlined. Type to change a link's words. Click it to open the link rail, and under **Goes to** choose a page of the site, or **Another address...** to type a web address, an email (`mailto:...`) or a phone number (`tel:...`). A link to a page follows the page if its address changes. The change is kept when you choose **Save as draft** or **Save and publish**. ## Tables In edit mode, tables that editors can change, such as a price list, are outlined. Click a cell, a column heading or a section heading and type. An empty heading says *Column heading* or *Section heading* in grey until you type in it. Under the table, **+ Row** adds a row at the end of the section you are in, **+ Column** adds a column on the right, and **+ Section** adds a section, with a heading, at the end. While a cell has focus, tabs beside the table open menus for what the cell belongs to. Pointing at a tab, or opening its menu, tints the cells it changes: - **Row**, on the row's left: add a row above or below, move it up or down (from one section into the next), or remove it. - **Column**, above the column: add a column to the left or right, move it, or remove it. - **Section**, on the right of the section's first row: add or remove its heading, add a section below, move it, or remove it with its rows. From the keyboard, Alt+F10 moves from a cell to its tabs, the arrow keys move through a menu, and Escape goes back. The last row of a section, the last column and the last section cannot be removed; their menu items are greyed out, and pointing at one says why. A table has at most 12 columns and 200 rows. A section heading left blank is dropped when the table is saved. The changes are kept when you choose **Save as draft** or **Save and publish**. ## Page settings In edit mode, choose **Page settings** to change the page's title, description and share image, as shown in search results and when the page is shared. Each text field shows the page's default as a grey placeholder; leave a field empty to keep the default. **Choose image** picks the share image in the media rail, and **Remove** takes it away. The settings are saved with the page: choose **Save as draft** or **Save and publish**. See [SEO page settings](../concepts/seo.md). ## Page history Choose **History** on the toolbar to open the history rail. It is on the toolbar while you view a page, not in edit mode, because previewing and restoring a version both leave the page. It lists the page's draft, if it has one, then every published version, newest first, with who published it and when. The version visitors see now is marked **Live**. - **Preview** shows that version of the page, as it was. Visitors still see the live version. - **Publish this version** makes an earlier version live again. Nothing is deleted or overwritten: SunBird publishes a copy of it as a new version (marked "Restored from version N"), and any draft you have is kept. ## For developers Edit mode needs nothing beyond [making a page editable](make-a-page-editable.md) and [signing in](sign-in-and-manage-editors.md): - The toolbar is added to the page's `<body>` by SunBird, for signed-in editors only. Visitors never get it, and the page's markup needs nothing extra. Keep the default `@addTagHelper` lines and a `<body>` element in the layout. - Edit mode is the page's own address with `?sb-edit=1`, and a preview of version N is `?sb-version=N`. For anyone who is not a signed-in editor both show the normal public page. - In edit mode each slot is wrapped so it can be outlined: `<sb-text>` becomes a `<span>`, `<sb-html>` a `<div>`, and `<sb-image>` keeps its `<img>` with extra attributes. The public page is rendered exactly as before. - Rich text is edited with HCI.RichText, an HCI package that comes with SunBird; in edit mode its script and stylesheet load from `/_content/HCI.RichText/`. The browser sends a structured document, never HTML, and SunBird writes the HTML from it using a fixed set of tags (`p`, `h2` to `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `strong`, `em`, `s`, `sup`, `sub`, `a`, `br`). Style those tags inside your `<sb-html>` slots. The editor offers only what SunBird accepts, and the server refuses anything else; images, tables and styles are not offered. Any other markup in a slot's default content (a class, a `<code>`) is dropped once an editor saves that slot, and edit mode warns the editor under the slot before then. - Pages shown to a signed-in editor are sent with `Cache-Control: no-store`, so no shared cache can serve the toolbar or a draft to visitors. - Saves go to `POST /sunbird/edit/{pageKey}/draft`, `/publish` and `/rollback`, for editors only, with an antiforgery token, and with the page's concurrency stamp so a stale save is refused. - Image editing needs a file store registered; see [Store images](store-images.md). An image slot in edit mode carries `data-sb-media` with its image's id, and a click on it never follows a link it sits in. - Edit mode only reads while showing a page. The database is written only when the editor saves. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/repeat-parts-of-a-page.html --> # Repeat parts of a page Some parts of a page are a list of the same thing: pictures in a gallery, testimonials, team members, the steps of a process. Write one entry's markup inside `<sb-repeater>`, and editors can add, remove and reorder the entries in edit mode. Each entry has its own copy of the slots inside it. ## 1. Wrap one entry ```cshtml <div class="gallery"> <sb-repeater key="gallery" count="3" max="24"> <figure> <sb-image key="picture" src="/img/placeholder.jpg" alt="" /> <figcaption><sb-text key="caption">A caption for the picture</sb-text></figcaption> </figure> </sb-repeater> </div> ``` - `key` names the repeater. It follows the slot key rules and must be unique on the page. - `count` is how many entries the page shows until an editor changes them. It defaults to 1. - `max` is the most entries an editor can add. Leave it out for no limit. The markup inside is each entry's default, as with any slot. Partials work inside a repeater too. ## How it renders The `<sb-repeater>` tag is not rendered. Its content is repeated once per entry, directly inside the parent element, so the page's CSS applies to the entries as it would to hand-written ones. Selectors such as `.gallery figure:nth-child(even)` alternate the entries' look, and keep alternating as editors add entries. ## How the content is stored Each entry is numbered from 1, and the slots inside it are stored under the repeater's key and the entry's number: `gallery.1.picture`, `gallery.1.caption`, `gallery.2.picture` and so on. The number of entries is saved with the page, so it is drafted, published, previewed and rolled back with the rest of the page. The numbers are positions. Removing entry 2 of 4 moves entries 3 and 4, with their content, to 2 and 3; moving an entry swaps the content of the two positions. The removed entry stays in the page's history. Inside an `<sb-scope>`, the scope's key comes first: `<sb-scope key="side">` around `<sb-repeater key="notes">` stores `side.notes.1.note`. Every full key must still be at most 100 characters, entry number included. ## How editors use it In edit mode a dashed frame goes round the whole repeating section: - **Add entry**, on the frame's bottom edge, adds an entry at the end, showing the markup defaults, ready to edit. It is unavailable at `max`. - Each entry has a small bar of buttons just above its top-right corner. **Up** and **Down** move the entry. - **Remove** removes the entry, after asking. The last entry cannot be removed. The page makes room for these buttons, so they never cover the entry or anything inside it, such as a shared fragment. Each of these is saved as a draft straight away, together with any other unsaved changes on the page, and the page reloads to show the new order. Visitors see the change when the page is published. ## Mistakes and how they show up These are logged as slot errors, and shown on the page when `SunBird:ShowSlotErrors` is on (see [Make a page editable](make-a-page-editable.md#mistakes-and-how-they-show-up)): - a repeater without a `key`, or with an invalid key - two repeaters, or a repeater and a slot, with the same key on one page - a repeater inside another repeater: repeaters cannot be nested - a repeater in a layout or a section - `count` or `max` below 1, or `count` above `max` With the setting off, a repeater used wrongly shows its markup once, as written. If `max` is lowered in code below the number of entries an editor has saved, the page shows only the first `max`. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/share-content-between-pages.html --> # Share content between pages Some content appears on many pages: testimonials, a call to action, the contact details in a footer. Make it a **fragment**, and editors change it once for every page that shows it. A fragment has its own drafts, versions and history, separate from any page's. ## 1. Write the fragment as a partial view Give the partial a fragment key with `@attribute`. Its markup is the fragment's default content, and any slots, repeaters, images and links inside it belong to the fragment: ```cshtml @* Pages/Shared/_Testimonials.cshtml *@ @attribute [HCI.SunBird.Web.SunBirdFragment("testimonials")] <section class="testimonials"> <h2><sb-text key="title">What our clients say</sb-text></h2> <sb-repeater key="quotes" count="3"> <figure> <blockquote><sb-text key="quote">A sentence from a happy client.</sb-text></blockquote> <figcaption><sb-text key="name">Client name</sb-text></figcaption> </figure> </sb-repeater> </section> ``` The key follows the page key rules: 1 to 100 ASCII letters, digits, `-`, `_` and `.`, unique among the site's fragments. The fragment's content is stored under it, so changing the key loses the content. ## 2. Show it wherever it belongs ```cshtml <sb-fragment name="_Testimonials" /> ``` `name` finds the partial as `<partial name="…" />` does. Fragments can go on pages, templates and **layouts**, so a fragment is also the way to make part of a header or footer editable: ```cshtml @* Pages/Shared/_Layout.cshtml *@ <footer><sb-fragment name="_Footer" /></footer> ``` The tag itself is not rendered. The slot keys inside a fragment are the fragment's own: a page's `title` slot and a fragment's `title` slot do not clash, and an `<sb-scope>` around a fragment does not change its keys. ## How editors change it 1. In edit mode, a fragment is framed and its slots cannot be edited from the page. The bar along the frame's bottom edge has **Edit fragment** and **Fragment history**. The page makes room for the bar, so it never covers what comes after. A fragment laid over other content, such as a badge in a card's corner, has no room to take: its bar shows while you point at the fragment or tab to it. 2. **Edit fragment** shows the page with only the fragment editable. The fragment's bar now has **Save as draft**, **Save and publish** and **Exit edit mode**: they save the fragment, for every page that shows it. The toolbar along the bottom is for the page, and offers **Edit this page** to go back to editing it. 3. **Fragment history** opens a rail like the page's history: preview a version on the page, or publish it again as a new version. Page history and fragment history are separate. Restoring an earlier version of a page never changes its fragments, because that would change other pages too. ## Mistakes and how they show up These are slot errors, shown on the page when `SunBird:ShowSlotErrors` is on (see [Make a page editable](make-a-page-editable.md#mistakes-and-how-they-show-up)): - `<sb-fragment>` without a `name`, or a name that finds no partial view - a partial without `[SunBirdFragment]`, or with an invalid key - a fragment inside another fragment - the same fragment twice on one page ## Storage Fragments are stored in their own tables, `Fragments`, `FragmentVersions` and `FragmentSlotValues` (with the `SunBird_` prefix on SQLite), added by the `AddFragments` migration. Like pages, a fragment has no rows until an editor first saves it, and public pages only read its published content, cached until it is next published (or, when published from another copy of the site, for up to 5 seconds more). --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/schedule-publishing.html --> # Schedule publishing Editors can save their changes to a page, an added page or a shared fragment now, and have them go live at a set date and time. A news article or an offer then appears when it should, with nobody there to publish it. Nothing needs setting up. Scheduling works on every SunBird site, with either database. ## Schedule changes 1. Open the page in edit mode and make the changes. 2. Choose **Schedule...** on the edit bar. For a shared fragment, it is on the fragment's own bar while you edit it. 3. Pick the date and time, then choose **Schedule**. The time is in the editor's own time zone, as their browser reports it. SunBird stores it in UTC. A time that has already passed is refused: publish now instead. Scheduling saves the changes at once, as a new numbered version. Visitors keep seeing the live version until the release time. A new page added from a template stays hidden, and out of `<sb-sub-pages>` lists, until then. Its address counts as taken from the moment it is scheduled, so no other page can claim it first. ## See, change and cancel a schedule - The edit bar says **Changes are scheduled to go live** with the time. - **History** lists the scheduled version with a **Scheduled** badge and **Goes live** with the time. **Cancel schedule** cancels it. The version stays in history, marked **Cancelled**, and visitors keep seeing the live version. The scheduled changes become the draft, unless there already is one, so the editor can carry on with them and schedule again. - To change the time, schedule again. The earlier schedule is cancelled, so a page or fragment has at most one schedule waiting. - Unpublishing or deleting a page cancels its schedule, so it does not come back. ## Editing today's page while a schedule waits Edit mode always shows today's page, the version visitors see now, not the scheduled changes. The edit bar says so while a schedule waits. **Save and publish** changes today's page and leaves the schedule alone. Publishing an older version from History does the same. At the release time the scheduled version replaces whatever is live, because it went live most recently. A fix published today is not in the scheduled version unless it was made there too. To change the scheduled changes, cancel the schedule, edit the draft it becomes, and schedule again. A new page that has not gone live yet has only its scheduled content, so edit mode starts from that. ## How a scheduled version goes live Nothing runs at the release time and nothing is written to the database. The live version of a page or fragment is the one that went live most recently: a published version at the moment it was published, a scheduled one at its release time. Every read works this out from the clock. Cached content (each page's slots, each fragment, and the tree of added pages) is kept only until the next release time it holds. The first view after that reads again. Every copy of the site sharing the database, such as staging and production, works out the same answer from its own clock. The site does not need to stay awake (App Service Always On) for a release to happen on time: the next visit after the release time shows it. > [!NOTE] > Every copy of the site checks every 5 seconds whether content changed elsewhere, so a schedule made on one copy is known to all of them within seconds, and goes live on each at its release time. ## History and version numbers A scheduled version takes its number when it is scheduled. A cancelled schedule keeps its number, so the published versions have a gap where it was. See [Drafts and history](../concepts/drafts-and-history.md). --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/create-pages-from-templates.html --> # Let editors add pages Most SunBird pages are built in code. A **template** lets editors add pages themselves, such as news stories: the developer builds the page once, says which pages it may go under, and an editor adds a new one from the edit bar of that page. Each added page has its own title, address and slot content, and is drafted, published, previewed and rolled back like any other page. ## 1. Build the template A template is an ordinary Razor page. Put `[SunBirdTemplate]` on its PageModel, in place of `[SunBirdPage]`: ```csharp [SunBirdTemplate("article", Name = "Article", Description = "A news story or blog post: a headline, an introduction, a picture and the story.")] public sealed class ArticleModel : PageModel { } ``` - The key (`"article"`) is stored with every page made from the template, so do not change it once editors have used it. It follows the page-key rules: 1 to 100 ASCII letters, digits, `-`, `_` and `.`, unique among the site's templates. - `Name` is what editors see if there is a choice of templates. `Description` is an optional sentence under it. Mark the editable parts with slots, as on any page. The markup content is each slot's default in every new page: ```cshtml @page @model ArticleModel <article class="section"> <h1><sb-text key="headline">A headline for the story</sb-text></h1> <sb-html key="intro"><p>One or two sentences that sum the story up.</p></sb-html> <sb-image key="picture" src="/img/hero.svg" alt="" /> <sb-html key="body"><p>The story itself.</p></sb-html> </article> ``` Put the template anywhere under `Pages/`, for example `Pages/Templates/Article.cshtml`. Its own file address answers 404: a template is reached only through the pages made from it. Put `<sb-seo />` in the layout's `<head>`, so each added page gets its title as its `<title>`. ## 2. Say where it may be added List the template in `ChildTemplates` on the page it goes under: ```csharp [SunBirdPage("news", Title = "News", ChildTemplates = ["article"])] public sealed class NewsModel : PageModel { } ``` That is all: there is nothing to register and no setting. A template can list `ChildTemplates` too, so a section made by an editor can have pages of its own. Only a page whose route has no parameters can have pages under it. `MapSunBird()` stops at startup if `ChildTemplates` names a template that does not exist, or if two templates share a key. ## 3. List the added pages Header, footer and navigation stay in code, so show the added pages where they belong, usually on the page they were added under: ```cshtml <sb-sub-pages class="news-list" /> ``` It renders the published sub-pages of the current page as links, newest first, and nothing if there are none: ```html <ul class="news-list"><li><a href="/news/opening-day">Opening day</a></li></ul> ``` `page="news"` lists another page's sub-pages, and `limit="3"` caps the list, for example for the latest news on the home page. For markup of your own, inject `ISunBirdPages` and call `GetSubPagesAsync("news")`: each `SunBirdPageLink` has the page's `Key`, `Title`, `Url` and `FirstPublishedUtc`. Both read from memory, so public pages still never write to the database. Drafts, unpublished pages and pages under an unpublished page are never listed. ## How editors add a page 1. Go to the page the new one belongs under, such as News. 2. Click **New page** on the edit bar. A blade opens on the right. 3. Type a title. The address fills itself in from the title (`/news/opening-day`); change it if you like. Choose the kind of page only if there is more than one. 4. Click **Create and edit**. The page opens in edit mode as a draft, which only editors can see until it is published. The blade also lists the pages already under this one, drafts included, so they are easy to find again. ## Addresses A page's address is its parent's address plus its own slug: lowercase letters, digits and single hyphens, at most 100 characters, with the whole address at most 200. An address that a code page or endpoint answers, or that another page uses, is refused. Code always wins. An address asked for in capitals redirects to the lowercase one. In edit mode, **Page settings** shows an added page's **Page title** and **Address**. They are saved with the page: - **Save as draft** keeps a new address in the draft. Visitors still see the page at its published address. - **Save and publish** moves the page, and every page under it moves with it. Old addresses then redirect (301) to the new ones. Rolling back to an earlier version restores that version's title and address too, unless another page has taken the address since. ## Unpublish and delete An added page's **Page settings** also has: - **Unpublish**: takes the page, and the pages under it, off the public site. Nothing is lost, editors can still open it, and publishing it again brings it back. - **Delete page**: asks first, then removes the page from the site and frees its address. Its history is kept in the database. A page with pages under it cannot be deleted until they are. Pages built in code cannot be unpublished or deleted. ## Staging Staging and production share one database, and an added page and its address changes are drafts until published. A page added or moved on staging therefore cannot appear on the live site until someone publishes it there. **Coming:** `<sb-nav>`, so editors can choose which pages show in the site's navigation. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/sign-in-and-manage-editors.html --> # Sign in and manage editors SunBird's sign-in is the site's sign-in. `AddSunBird()` sets up ASP.NET Identity, stores the accounts in SunBird's own tables, and adds the sign-in and account pages. There is no seeded account and no default password. SunBird emails editors their invites and password reset links, so the site needs a way to send email. ## What the site needs SunBird's pages are Razor Pages inside the `HCI.SunBird` package, and it sends email through HCI.Email. In `Program.cs`: ```csharp builder.Services.AddRazorPages(); builder.Services.AddSendGridEmail(builder.Configuration); // HCI.Email.SendGrid, or any HCI.Email IEmailService builder.AddSunBird().UseSqlServer(builder.Configuration.GetConnectionString("SunBird")!); // ... app.MapSunBird(); app.UseStaticFiles(); // or app.MapStaticAssets(); serves SunBird's stylesheet app.MapRazorPages(); // maps SunBird's pages as well as the site's own ``` and in configuration: ```json { "SunBird": { "SiteUrl": "https://www.example.co.uk", "Email": { "From": "noreply@example.co.uk" } } } ``` `SiteUrl` is the site's public address. The links in SunBird's emails are built from it, never from the address a request came in on, so a forged request cannot send an editor a real email that links to someone else's site. Each page's canonical address is built from it too (see [SEO page settings](../concepts/seo.md)). On App Service, set it as a deployment slot setting (`SunBird__SiteUrl`), so emails sent from staging link to staging. `MapSunBird()` stops the site starting, with a clear message, if no HCI.Email `IEmailService` is registered or `From` or `SiteUrl` is missing. ASP.NET Core adds the authentication and authorization middleware by itself. If the site calls `app.UseRouting()` itself, also call `app.UseAuthentication()` and `app.UseAuthorization()` after it. ### On a developer's machine To follow invite and reset links locally without a mail provider, register an `IEmailService` that writes each email to the log, as the sample site does: ```csharp builder.Services.AddSingleton<IEmailService, LogEmailService>(); ``` The sign-in cookie is Secure, so sign in over https (`https://localhost:...`), not plain http. ## The pages | Page | Who | What it does | |---|---|---| | `/sunbird/setup` | Anyone, once | Creates the first admin, given the setup code. Closed for good once any account exists. | | `/sunbird/login` | Anyone | Signs in with email and password. Sends people to setup while no account exists. | | `/sunbird/logout` | Signed in | Signs out. Signing out takes a POST, so a link elsewhere cannot sign an editor out. | | `/sunbird/forgot-password` | Anyone | Emails a link to choose a new password. | | `/sunbird/reset-password` | From the email | Chooses a new password. | | `/sunbird/invite` | From the email | An invited user chooses their password and is signed in. | | `/sunbird/confirm-email` | From the email | Confirms a change of email. | | `/sunbird/account` | Signed in | Change your own password, display name and email. | | `/sunbird/users` | Admins | Invite and edit users; resend an invite; set a one-time password; make and revoke [access tokens for agents](let-agents-edit-content.md); suspend and reactivate. | The pages carry `noindex`, and use their own small stylesheet, not the site's layout. ## Roles | Role | Can | |---|---| | Editor | Edit content, and manage their own account. | | Admin | Everything an editor can, and manage users. | The first account is an admin. An admin cannot suspend themselves or remove their own admin role, and the last active admin cannot be suspended or made an editor, so a site always has someone who can manage users. ## 1. Create the first admin Set a setup code in the site's configuration. Keep it out of source control: use an App Service setting, or user secrets on a developer's machine. | Where | Setting | |---|---| | App Service | `SunBird__SetupCode` = a long random value | | Developer's machine | `dotnet user-secrets set "SunBird:SetupCode" "<a long random value>"` | Then open `/sunbird/setup`, enter the code, your email, display name and password, and choose **Create admin**. You are signed in and taken to the users page. Wrong setup codes count like failed sign-ins: after `SunBird:Lockout:MaxFailedAttempts` wrong codes, setup refuses every code for `SunBird:Lockout:Minutes`. Once the first account exists, setup is closed even if the code is still set. You can then remove the setting. Staging and the live site share one database, so the first admin only needs creating once, on either. ## 2. Invite editors On `/sunbird/users`, choose **Invite a user**, and enter their email, display name and role. SunBird emails them a link to choose their own password. The link works once, for 3 days, and using it confirms their email. Until then the user shows as **Invited** and cannot sign in. If the invite email cannot be sent, the user is still saved, and SunBird says so. Fix the email settings, then open the user and choose **Resend invite**. A link that has run out is replaced the same way. ## 3. Forgotten passwords **Forgotten your password?** on the sign-in page leads to `/sunbird/forgot-password`. SunBird emails a link to choose a new password. The link works once, for 2 hours, and using it signs out the account's other sessions. The page always says the same thing, whether or not an account uses that email, so it cannot be used to find out who has an account. Nothing is sent to a suspended account or one that has not accepted its invite. ## 4. Change your email On `/sunbird/account`, enter the new email and your current password. SunBird emails a link to the new address; your email, which is also what you sign in with, changes only when you use it. The link works once, for 24 hours. ## 5. When email is not working An admin can open a user and set a **one-time password**, and give it to them in person. It replaces their password (and works for a user who has not accepted their invite yet). When they sign in, SunBird asks them to choose their own password before they can do anything else. ## 6. Suspend or reactivate an account Open the account from `/sunbird/users` and choose **Suspend**. A suspended account cannot sign in, its open sessions end within a minute, and its [agent access tokens](let-agents-edit-content.md) stop working at once. Accounts are never deleted, because the page history records who saved and published each version. **Reactivate** lets the account sign in again. ## Branding the emails SunBird's emails can carry the client's name, logo, colour and footer through the `SunBird:Email` settings `BrandName`, `LogoUrl`, `AccentColour` and `FooterText`. See [Settings](../settings.md). > [!NOTE] > **Coming:** replacing SunBird's email templates with a site's own. ## Passwords and lockout By default a password needs at least 12 characters, and five failed sign-ins in a row lock an account for 15 minutes. Change either per site in configuration; see [Settings](../settings.md). ## Staying signed in across restarts and slot swaps The sign-in cookie is protected by the site's ASP.NET Core Data Protection keys. If the keys are not kept somewhere every instance and deployment slot shares, editors are signed out when the site restarts or the slots swap. Configure Data Protection key storage in the site's `Program.cs`. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/let-agents-edit-content.html --> # Let agents edit content over MCP SunBird has an MCP server at `/sunbird/mcp`. An AI agent, such as a web factory agent porting an old site's content, uses it to add pages from templates, fill in their slots, change fragments, upload images, save drafts, and publish, schedule, roll back, unpublish or delete. Every action an editor can take in edit mode has a tool. It works as one editor account, through the same code as edit mode: the same rules, the same messages, and every change in history under that editor's name. The agent never writes to the database itself. The site's structure is still built in code: the pages, templates, slots and fragments in the Razor markup. The MCP server fills that structure with content. ## 1. Turn the server on The server is off unless `SunBird:Mcp:Enabled` is `true`. Turn it on where agents work, in development and on the staging slot, and leave it off on the live site: ```json { "SunBird": { "Mcp": { "Enabled": true } } } ``` On Azure App Service, set `SunBird__Mcp__Enabled` as a **deployment slot setting** on the staging slot, so it stays with the slot and a swap never carries it to the live site. While it is off, `/sunbird/mcp` answers 404. | Setting | Default | What it does | |---|---|---| | `Mcp:Enabled` | `false` | Serves the MCP server at `/sunbird/mcp`. | | `Mcp:AllowPublish` | `true` | When `false`, agents can only save drafts: the tools that change what visitors see (publish, schedule, cancel a schedule, roll back, unpublish and delete a page) are not offered, and are refused if called. | On a site in [staging mode](../concepts/staging.md), those tools are never offered, whatever `Mcp:AllowPublish` says, because staging shares the live database. Agents save drafts there, and an editor publishes them on the live site. ## 2. Make an access token An admin makes a token on the user's page in **Users** (`/sunbird/users`), under **Agent access**: a name, such as "Web factory", and how long it works (7 days to a year). The token is shown once, when it is made. Copy it then: only a hash of it is kept, so it cannot be shown again. The agent acts as that user. Make a separate editor account for agents, such as "Web factory", so history shows which changes an agent made. Revoke a token on the same page; it stops working at once. Suspending the account stops all its tokens too. A call with no token, an expired or revoked one, or one for a suspended account gets 401. ## 3. Connect the agent The server speaks MCP over Streamable HTTP. Give the agent the address and the token as a bearer token. For Claude Code, for example: ```bash claude mcp add --transport http sunbird https://staging.example.co.uk/sunbird/mcp --header "Authorization: Bearer sbmcp_..." ``` Keep the token out of source control, as you would a password. ## 4. Work on the content | Tool | What it does | |---|---| | `whoami` | The account the token acts as, and whether publishing is allowed. | | `search_icons` | Finds icons by name or tag for an `sb-icon` slot. | | `list_templates` | The templates pages can be made from, and which templates may go under each. | | `list_pages` | Every code page and created page (drafts included), with its address and the templates that may be added under it. | | `get_page` | A page's saved slots and settings (the draft, or else what is live), its versions, and its stamp. | | `create_page` | Adds a page from a template under a parent, as a draft. Returns its key and address. | | `save_page_draft` | Saves slots, repeater entries, page settings and a created page's title and slug as the draft. | | `publish_page` | Publishes the page's draft. | | `schedule_page` | Saves any changes given as the draft and schedules it to go live at a time (UTC). | | `cancel_page_schedule` | Cancels a waiting schedule; its content becomes the draft if there is none. | | `roll_back_page` | Publishes a copy of an earlier version as the next version. | | `unpublish_page` | Takes a created page off the public site, keeping everything. | | `delete_page` | Deletes a created page (its history is kept). Refused while it has pages under it. | | `list_fragments` | Every fragment, with the partial view that holds its slots. | | `get_fragment` | A fragment's saved slots, versions and stamp. | | `save_fragment_draft` | Saves a fragment's slots and repeater entries as its draft. | | `publish_fragment` | Publishes the fragment's draft, for every page that shows it. | | `schedule_fragment` | Schedules the fragment's draft to go live at a time (UTC). | | `cancel_fragment_schedule` | Cancels the fragment's waiting schedule. | | `roll_back_fragment` | Publishes a copy of an earlier version of the fragment. | | `list_media` | The media library. | | `upload_media` | Uploads an image (JPEG, PNG, WebP or GIF) or a document (PDF), sent as base64. Returns the item and its id. | | `delete_media` | Deletes an item. Refused while a live version or draft uses it; if only older versions do, call again with `confirm` true. | For example, to add a news article under a News page that allows the `article` template: 1. `create_page` with `parentKey` `news` and `title` `Opening day`. It returns the page's key and address (`/news/opening-day`). 2. `get_page` with that key, for its stamp. 3. `save_page_draft` with the key, the stamp and the slots to fill in. 4. `publish_page` with the key and the stamp the save returned. ### Slots `get_page` and `get_fragment` list only the slots that have been saved. A slot never saved shows its default from the markup, so the agent takes slot keys from the page's markup. A slot's full key includes any `sb-scope` prefix and, inside a repeater, the entry number: `team.2.name`. In a fragment, keys are the fragment's own. A save sends only the slots that change, each with its key and kind: ```json [ { "key": "headline", "kind": "text", "text": "We are open" }, { "key": "photo", "kind": "image", "mediaItemId": "3f2b...", "alt": "The surgery's front desk" }, { "key": "more", "kind": "link", "text": "Book now", "target": "page:p-abc123def456ghij" }, { "key": "leaflet", "kind": "file", "text": "Price list (PDF)", "mediaItemId": "9c1d..." }, { "key": "service", "kind": "icon", "text": "dentistry" }, { "key": "intro", "kind": "richtext", "document": { "blocks": [ { "type": "heading", "level": 2, "content": [ { "text": "Opening times" } ] }, { "type": "paragraph", "content": [ { "text": "Open " }, { "text": "every day", "bold": true } ] } ] } } ] ``` Rich text is always an HCI.RichText document, never HTML. SunBird writes the HTML from it (see [Pages and slots](../concepts/pages-and-slots.md)), and a document SunBird does not accept is refused with a message saying why. An empty `alt` marks a decorative image. A link's `target` is `page:` and a page key, so the link follows the page when its address changes, or an address; empty keeps the markup's address. To add, remove or move a repeater's entries, send `repeaters`: for each repeater, `order` gives, for each new position, the entry number it had before, or `null` for a new entry. `[1, 2, null]` adds an entry at the end; `[2, 1]` swaps two. Slot keys in the same save use the new entry numbers. ### Page settings `settings` replaces a page's title, description and share image for search and sharing as a whole: `{ "title": "...", "description": "...", "shareImageId": "..." }`. An empty title or description means the page's default from code. A created page's own title and address go in `page`: `{ "title": "...", "slug": "..." }`. ### Stamps and errors Every page and fragment has a stamp. Send the one `get_page`, `get_fragment` or the last save gave you (`null` for a page or fragment never saved). If someone else changed it since, the save is refused, as it would be for an editor, so read it again and repeat the change. A refused change is a tool error carrying the message an editor would see in edit mode: a stale stamp, an address that is taken, a template not allowed under that parent, a file that is not an image, a missing media item. A page or fragment key the site does not have is refused, so a typo cannot store content no page shows. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/store-images.html --> # Store images Editors upload images to the [media library](../concepts/media-library.md). SunBird keeps the files through the site's HCI.FileStorage `IFileStorageService`, so the site must register one. `MapSunBird()` stops the site from starting without one, with a message saying so. ## Register a file store For Azure Blob Storage, add `HCI.FileStorage.AzureBlobStorage` and register it in `Program.cs`, before `AddSunBird()`: ```csharp using HCI.FileStorage.AzureBlobStorage; builder.Services.AddAzureBlobStorage(builder.Configuration); builder.AddSunBird().UseSqlServer(builder.Configuration.GetConnectionString("SunBird")!); ``` See the package's own README for its settings (connection string and container). The container should not allow public access: visitors get images from SunBird, never from the store directly. SunBird stores its files under `sunbird/media/`, so it can share a store that the site uses for other files. > [!NOTE] > **Coming:** a database-backed `IFileStorageService` in the HCI FileStorage library, so a site can keep its images in its > own database with no extra service. Until then, register a provider such as Azure Blob Storage. For local development, > the sample site registers a small store that writes to `App_Data/files`. ## Set the size limit Uploads are limited to 10 MB by default. Change it with `SunBird:Media:MaxUploadMegabytes`: ```json { "SunBird": { "Media": { "MaxUploadMegabytes": 5 } } } ``` The limit also caps the size of the upload request, so a larger file is cut off rather than read. If the site sits behind a proxy or App Service front end with its own request size limit, keep that limit above this one. SunBird does not resize images: editors upload them at the size the page needs. ## What SunBird accepts Only JPEG, PNG, WebP and GIF images and PDF documents are accepted. SunBird reads the type from the file's first bytes, never from its name or the type the browser claims, so a script renamed `photo.png` is refused. SVG is refused, because an SVG file can carry script. An upload whose bytes match a file already in the library reuses that file instead of storing a copy. ## How images are served A published image slot renders `<img src="/sunbird/media/{id}">`. That address is public, and serves any image in the library that has not been deleted; the ids are random, so unpublished images cannot be guessed. Each response: - has the content type SunBird found from the bytes, and `X-Content-Type-Options: nosniff`, so a browser never treats it as anything else; - has `Content-Security-Policy: default-src 'none'; sandbox`, so even opened on its own it cannot run anything; - is cached for a year (`Cache-Control: public, max-age=31536000, immutable`), because an image's bytes never change. A changed picture is a new image with a new address. A document chosen for an `<sb-file>` link is served at `/sunbird/media/{id}/{file-name}` with the same headers, plus `Content-Disposition: inline` and the name it was uploaded with, so it opens in the browser and saves under that name. A deleted image or document returns 404. Serving only reads; it never writes to the database. ## Editor endpoints The media rail uses `GET /sunbird/edit/media` (the library), `POST /sunbird/edit/media` (a multipart upload with the file in the `file` field, and `kind=document` from the document picker) and `POST /sunbird/edit/media/{id}/delete`. They are for signed-in editors only and need the antiforgery token from the toolbar, like the other edit endpoints. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/store-content-in-sql-server.html --> # Store content in SQL Server SQL Server is SunBird's default database. Client sites in production use Azure SQL in the elastic pool, and developers can use SQL Server LocalDB on their own machine. ## 1. Add the packages ```shell dotnet add package HCI.SunBird dotnet add package HCI.SunBird.Data.SqlServer ``` ## 2. Add a connection string In `appsettings.json`, or in the App Service configuration for a deployed site: ```json { "ConnectionStrings": { "SunBird": "Server=(localdb)\\MSSQLLocalDB;Database=MySite;Trusted_Connection=True" } } ``` The database user needs rights to create and alter tables (for example `db_ddladmin`), because SunBird applies its own migrations at startup. ## 3. Register SunBird and map it In `Program.cs`: ```csharp using HCI.SunBird.Data.SqlServer; using HCI.SunBird.Web; var builder = WebApplication.CreateBuilder(args); builder.AddSunBird().UseSqlServer(builder.Configuration.GetConnectionString("SunBird")!); var app = builder.Build(); app.MapSunBird(); app.Run(); ``` `MapSunBird()` applies any pending SunBird migrations before the site starts serving requests. If no storage package was configured, it fails at startup with an error that says so. ## What SunBird creates - SunBird's tables live in their own `sunbird` schema: `sunbird.Pages`, `sunbird.PageVersions` and `sunbird.SlotValues`, and for fragments `sunbird.Fragments`, `sunbird.FragmentVersions` and `sunbird.FragmentSlotValues`. They can share a database with the site's own tables without clashing. - SunBird records its migrations in `sunbird.__SunBirdMigrationsHistory`, separate from any EF Core migrations the site has of its own. - A page's rows appear only when an editor first saves it. Until then, every slot shows its markup default. See [Pages and slots](../concepts/pages-and-slots.md). ## Upgrades and the shared database The staging slot and production share one database during a release (see [Staging mode](../concepts/staging.md)). Each SunBird release therefore only adds to the schema. It never drops, renames or changes a column the previous release still uses. When the new release starts in staging and migrates the database, production keeps working on the old code until the swap. ## If two editors save the same page SunBird checks for conflicting changes. If a page was changed by someone else after it was opened, the later save is refused instead of overwriting their work. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/how-to/store-content-in-sqlite.html --> # Store content in SQLite SQLite keeps SunBird's content in a single file next to the site, with no database server. Use it for small sites and local development. Client sites in production on Azure use [SQL Server](store-content-in-sql-server.md). ## 1. Add the packages ```shell dotnet add package HCI.SunBird dotnet add package HCI.SunBird.Data.Sqlite ``` ## 2. Add a connection string In `appsettings.json`: ```json { "ConnectionStrings": { "SunBird": "Data Source=App_Data/sunbird.db" } } ``` SunBird creates the database file if it does not exist, but the folder (`App_Data` here) must exist. The site needs write access to the folder, because SQLite writes a journal file beside the database. Keep the database file out of source control, and out of `wwwroot` so it is never served. ## 3. Register SunBird and map it In `Program.cs`: ```csharp using HCI.SunBird.Data.Sqlite; using HCI.SunBird.Web; var builder = WebApplication.CreateBuilder(args); builder.AddSunBird().UseSqlite(builder.Configuration.GetConnectionString("SunBird")!); var app = builder.Build(); app.MapSunBird(); app.Run(); ``` `MapSunBird()` applies any pending SunBird migrations before the site starts serving requests. If no storage package was configured, it fails at startup with an error that says so. ## What SunBird creates - SQLite has no schemas, so SunBird's table names start with `SunBird_`: `SunBird_Pages`, `SunBird_PageVersions` and `SunBird_SlotValues`, and for fragments `SunBird_Fragments`, `SunBird_FragmentVersions` and `SunBird_FragmentSlotValues`. They can share a database file with the site's own tables without clashing. - SunBird records its migrations in `__SunBirdMigrationsHistory`, separate from any EF Core migrations the site has of its own. - While it migrates, EF Core holds a lock in a `__EFMigrationsLock` table. That table is shared with the site's own EF Core migrations, if it has any, which is safe: they just take turns. - A page's rows appear only when an editor first saves it. Until then, every slot shows its markup default. See [Pages and slots](../concepts/pages-and-slots.md). ## Upgrades Each SunBird release only adds to the schema. It never drops, renames or changes a column the previous release still uses, so a database file works with the release before and the release after. ## If two editors save the same page SunBird checks for conflicting changes. If a page was changed by someone else after it was opened, the later save is refused instead of overwriting their work. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/settings.html --> # Settings SunBird reads its settings from the `SunBird` section of the site's configuration. Put them in `appsettings.json`: ```json { "SunBird": { "ShowSlotErrors": false } } ``` or set them as environment variables or App Service application settings, with a double underscore between the section and the key: `SunBird__ShowSlotErrors`. | Setting | Default | What it does | |---|---|---| | `SiteUrl` | none (required) | The site's public address, for example `https://www.example.co.uk`. The links in SunBird's emails, and the canonical link, `og:url` and `og:image` that `<sb-seo />` renders, are built from it, never from the address a request came in on. Set it as a deployment slot setting, so staging's links name staging. | | `ShowSlotErrors` | `true` in Development, `false` elsewhere | When on, a slot used wrongly (no page key, a missing, invalid or duplicate key, or a slot in a layout) is replaced on the page by a red box saying what is wrong. When off, the slot shows its markup default. Either way the problem is logged. | | `Mode` | `Live` | `Live` or `Staging`. Set `Staging` on a staging slot that shares the live database, as a deployment slot setting. Editors there see a banner and can save drafts, but nothing can be published, scheduled, restored, unpublished or deleted. Any other value stops the site starting. See [Staging mode](concepts/staging.md). | | `SetupCode` | none | The code `/sunbird/setup` asks for to create the first admin. Setup is closed for good once any account exists. Set it as an App Service setting or in user secrets, never in source control. See [Sign in and manage editors](how-to/sign-in-and-manage-editors.md). | | `Password:RequiredLength` | `12` | The shortest password an editor account can have. | | `Password:RequireDigit` | `false` | When `true`, a password needs a digit (0-9). | | `Password:RequireLowercase` | `false` | When `true`, a password needs a lowercase letter. | | `Password:RequireUppercase` | `false` | When `true`, a password needs an uppercase letter. | | `Password:RequireNonAlphanumeric` | `false` | When `true`, a password needs a character that is not a letter or digit. | | `Password:RequiredUniqueChars` | `1` | The number of different characters a password needs. | | `Lockout:MaxFailedAttempts` | `5` | Failed sign-in attempts in a row before an account is locked. | | `Lockout:Minutes` | `15` | How long a locked account stays locked. | | `Email:From` | none (required) | The address SunBird's emails come from. | | `Email:FromName` | the site's host name | The name shown with `Email:From`. | | `Email:BrandName` | the site's host name | The name at the top of SunBird's emails, for example the client's company. | | `Email:LogoUrl` | none | An absolute https address of a logo shown at the top of SunBird's emails instead of the brand name. | | `Email:AccentColour` | `#0b5cad` | The colour of buttons and the top border, as `#rgb` or `#rrggbb`. | | `Email:FooterText` | "Sent by the editing tools of" and the brand name | The line at the bottom of SunBird's emails. | | `Media:MaxUploadMegabytes` | `10` | The largest image an editor can upload, in megabytes. See [Store images](how-to/store-images.md). | | `Mcp:Enabled` | `false` | Serves the MCP server at `/sunbird/mcp`, through which AI agents with an access token work on content. Turn it on in development and on the staging slot, as a deployment slot setting, and leave it off on the live site. See [Let agents edit content over MCP](how-to/let-agents-edit-content.md). | | `Mcp:AllowPublish` | `true` | When `false`, agents can only save drafts through MCP: publishing, scheduling, rolling back, unpublishing and deleting pages are refused. They are refused in staging mode whatever this says. | ## Password and lockout rules SunBird's defaults follow current guidance: a long password matters more than a mix of character types, so the default is at least 12 characters with no other rules. A site that needs stricter rules sets them without a code change, for example: ```json { "SunBird": { "Password": { "RequiredLength": 16, "RequireDigit": true }, "Lockout": { "MaxFailedAttempts": 3, "Minutes": 30 } } } ``` As an App Service setting, nested keys also use double underscores: `SunBird__Password__RequiredLength`. The rules apply when a password is set or changed, so existing passwords are not checked again. ## Email SunBird sends invites, password reset links and email change confirmations through the site's HCI.Email `IEmailService`. `Email:From` and `SiteUrl` are required: the site does not start without them. The branding settings are optional and change only how the emails look: ```json { "SunBird": { "SiteUrl": "https://www.acmebakery.co.uk", "Email": { "From": "noreply@acmebakery.co.uk", "BrandName": "Acme Bakery", "LogoUrl": "https://www.acmebakery.co.uk/img/logo.png", "AccentColour": "#c0392b", "FooterText": "Acme Bakery, 1 High Street, York" } } } ``` The site does not start if `AccentColour` is not a hex colour or `LogoUrl` is not an absolute https address, because both go into the emails' markup. See [Sign in and manage editors](how-to/sign-in-and-manage-editors.md). ## Development, staging and live A typical site sets `ShowSlotErrors` like this: | Where | `ShowSlotErrors` | How | |---|---|---| | Developer's machine | on | The Development default, or `true` in `appsettings.Development.json`. | | Staging slot | on | An App Service setting `SunBird__ShowSlotErrors = true`, marked as a deployment slot setting so it stays with staging when the slots swap. | | Live site | off | The default, or `false` in `appsettings.json`. | Set `Mode` on the staging slot too, as `SunBird__Mode = Staging` (a deployment slot setting), so a staging slot that shares the live database cannot publish. Staging is the final catch: turn the setting on there and a mistake that got past development is visible on the page, before the release is swapped live. On the live site a mistake never spoils a page. The connection string is set separately, in `ConnectionStrings`. See [Store content in SQL Server](how-to/store-content-in-sql-server.md) or [Store content in SQLite](how-to/store-content-in-sqlite.md). --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/index.html --> # API reference Every public type and member of the SunBird packages, generated from the XML documentation in the code. SunBird keeps its public contract deliberately small. Implementations are internal; what is public is mainly the setup extensions, the tag helpers, the `[SunBirdPage]` attribute, options classes and a few interfaces. The packages have no public members yet. Each one appears here as it is released. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Data.SqlServer.SunBirdSqlServerExtensions.html --> # <a id="HCI_SunBird_Data_SqlServer_SunBirdSqlServerExtensions"></a> Class SunBirdSqlServerExtensions Namespace: [HCI.SunBird.Data.SqlServer](HCI.SunBird.Data.SqlServer.md) Assembly: HCI.SunBird.Data.SqlServer.dll Stores SunBird content in SQL Server. ```csharp public static class SunBirdSqlServerExtensions ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [SunBirdSqlServerExtensions](HCI.SunBird.Data.SqlServer.SunBirdSqlServerExtensions.md) #### Inherited Members [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.MemberwiseClone\(\)](https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Methods ### <a id="HCI_SunBird_Data_SqlServer_SunBirdSqlServerExtensions_UseSqlServer_HCI_SunBird_Web_ISunBirdBuilder_System_String_"></a> UseSqlServer\(ISunBirdBuilder, string\) Stores SunBird content in the SQL Server database at <code class="paramref">connectionString</code>. SunBird's tables go in the <code>sunbird</code> schema, and <code>MapSunBird()</code> creates and migrates them at startup. ```csharp public static ISunBirdBuilder UseSqlServer(this ISunBirdBuilder builder, string connectionString) ``` #### Parameters `builder` ISunBirdBuilder The builder returned by <code>AddSunBird()</code>. `connectionString` [string](https://learn.microsoft.com/dotnet/api/system.string) The SQL Server connection string. The user needs rights to create and alter tables. #### Returns ISunBirdBuilder The same <code class="paramref">builder</code>, for chaining. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Data.SqlServer.html --> # <a id="HCI_SunBird_Data_SqlServer"></a> Namespace HCI.SunBird.Data.SqlServer ### Classes [SunBirdSqlServerExtensions](HCI.SunBird.Data.SqlServer.SunBirdSqlServerExtensions.md) Stores SunBird content in SQL Server. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Data.Sqlite.SunBirdSqliteExtensions.html --> # <a id="HCI_SunBird_Data_Sqlite_SunBirdSqliteExtensions"></a> Class SunBirdSqliteExtensions Namespace: [HCI.SunBird.Data.Sqlite](HCI.SunBird.Data.Sqlite.md) Assembly: HCI.SunBird.Data.Sqlite.dll Stores SunBird content in SQLite. ```csharp public static class SunBirdSqliteExtensions ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [SunBirdSqliteExtensions](HCI.SunBird.Data.Sqlite.SunBirdSqliteExtensions.md) #### Inherited Members [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.MemberwiseClone\(\)](https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Methods ### <a id="HCI_SunBird_Data_Sqlite_SunBirdSqliteExtensions_UseSqlite_HCI_SunBird_Web_ISunBirdBuilder_System_String_"></a> UseSqlite\(ISunBirdBuilder, string\) Stores SunBird content in the SQLite database at <code class="paramref">connectionString</code>. SunBird's table names start with <code>SunBird_</code>, and <code>MapSunBird()</code> creates and migrates them at startup. ```csharp public static ISunBirdBuilder UseSqlite(this ISunBirdBuilder builder, string connectionString) ``` #### Parameters `builder` ISunBirdBuilder The builder returned by <code>AddSunBird()</code>. `connectionString` [string](https://learn.microsoft.com/dotnet/api/system.string) The SQLite connection string, for example <code>Data Source=App_Data/sunbird.db</code>. The database file is created if it does not exist, but its folder must exist. #### Returns ISunBirdBuilder The same <code class="paramref">builder</code>, for chaining. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Data.Sqlite.html --> # <a id="HCI_SunBird_Data_Sqlite"></a> Namespace HCI.SunBird.Data.Sqlite ### Classes [SunBirdSqliteExtensions](HCI.SunBird.Data.Sqlite.SunBirdSqliteExtensions.md) Stores SunBird content in SQLite. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.ISunBirdBuilder.html --> # <a id="HCI_SunBird_Web_ISunBirdBuilder"></a> Interface ISunBirdBuilder Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Configures SunBird after <xref href="HCI.SunBird.Web.SunBirdHostBuilderExtensions.AddSunBird(Microsoft.Extensions.Hosting.IHostApplicationBuilder)" data-throw-if-not-resolved="false"></xref>. A data package adds the extension method that chooses the database, such as <code>UseSqlServer(connectionString)</code> from <code>HCI.SunBird.Data.SqlServer</code>. ```csharp public interface ISunBirdBuilder ``` ## Properties ### <a id="HCI_SunBird_Web_ISunBirdBuilder_Services"></a> Services The host's service collection, where SunBird and its data package register their services. ```csharp IServiceCollection Services { get; } ``` #### Property Value [IServiceCollection](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection) --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.ISunBirdPages.html --> # <a id="HCI_SunBird_Web_ISunBirdPages"></a> Interface ISunBirdPages Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Reads the pages editors created from templates, for listings with markup of the site's own, such as news cards. Inject it into a view (<code>@inject ISunBirdPages Pages</code>) or a PageModel. For a plain list of links, use <code><sb-sub-pages /></code>. Reads are cached in memory, so a public page view never writes to the database. ```csharp public interface ISunBirdPages ``` ## Methods ### <a id="HCI_SunBird_Web_ISunBirdPages_GetSubPagesAsync_System_String_System_Threading_CancellationToken_"></a> GetSubPagesAsync\(string, CancellationToken\) The published pages added under a page, newest first by when they were first published. Only pages the public can see are included: drafts, unpublished pages and pages under an unpublished page are left out. ```csharp Task<IReadOnlyList<SunBirdPageLink>> GetSubPagesAsync(string pageKey, CancellationToken cancellationToken = default) ``` #### Parameters `pageKey` [string](https://learn.microsoft.com/dotnet/api/system.string) The parent page's key: a code page's <xref href="HCI.SunBird.Web.SunBirdPageAttribute.Key" data-throw-if-not-resolved="false"></xref>, or a created page's key. `cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken) Cancels the read. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task\-1)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[SunBirdPageLink](HCI.SunBird.Web.SunBirdPageLink.md)\>\> The sub-pages, or an empty list if there are none or the page does not exist. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbFileTagHelper.html --> # <a id="HCI_SunBird_Web_SbFileTagHelper"></a> Class SbFileTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll An editable download link, such as a form or leaflet: <code><sb-file key="referral-form" href="/docs/referral-form.pdf" show-size>Referral form</sb-file></code>. Renders an <code><a></code> that keeps every attribute other than <code>key</code>, <code>href</code> and <code>show-size</code>. Editors change its text and choose the PDF it gives from the media library. Without a saved value the link uses its own <code>href</code> and content. ```csharp [HtmlTargetElement("sb-file")] public sealed class SbFileTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbFileTagHelper](HCI.SunBird.Web.SbFileTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbFileTagHelper_Href"></a> Href The default address, used until an editor chooses a document. ```csharp [HtmlAttributeName("href")] public string? Href { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ### <a id="HCI_SunBird_Web_SbFileTagHelper_Key"></a> Key The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any <code>sb-scope</code> prefix. ```csharp [HtmlAttributeName("key")] public string? Key { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ### <a id="HCI_SunBird_Web_SbFileTagHelper_ShowSize"></a> ShowSize Adds the chosen document's type and size after the link text, such as "(PDF, 147 KB)", in a <code><span class="sb-file-size"></code>. Nothing is added while the link uses its default address. ```csharp [HtmlAttributeName("show-size")] public bool ShowSize { get; set; } ``` #### Property Value [bool](https://learn.microsoft.com/dotnet/api/system.boolean) ## Methods ### <a id="HCI_SunBird_Web_SbFileTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbFragmentTagHelper.html --> # <a id="HCI_SunBird_Web_SbFragmentTagHelper"></a> Class SbFragmentTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Shows a fragment: a partial view with <xref href="HCI.SunBird.Web.SunBirdFragmentAttribute" data-throw-if-not-resolved="false"></xref>, whose content is shared by every page that shows it: <code><sb-fragment name="_Testimonials" /></code>. The slots inside belong to the fragment, not the page, so an editor changes them once for every page. It can be used on pages, templates and layouts. The tag itself is not rendered. ```csharp [HtmlTargetElement("sb-fragment", TagStructure = TagStructure.WithoutEndTag)] public sealed class SbFragmentTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbFragmentTagHelper](HCI.SunBird.Web.SbFragmentTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbFragmentTagHelper_Name"></a> Name The partial view's name or path, as for <code><partial name="…" /></code>. ```csharp [HtmlAttributeName("name")] public string? Name { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ## Methods ### <a id="HCI_SunBird_Web_SbFragmentTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbHtmlTagHelper.html --> # <a id="HCI_SunBird_Web_SbHtmlTagHelper"></a> Class SbHtmlTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll An editable rich-text slot: <code><sb-html key="intro"><p>Default</p></sb-html></code>. Renders the slot's published HTML, which SunBird wrote from an HCI.RichText document when it was saved, or else its own content. The tag itself is not rendered. ```csharp [HtmlTargetElement("sb-html")] public sealed class SbHtmlTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbHtmlTagHelper](HCI.SunBird.Web.SbHtmlTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbHtmlTagHelper_Key"></a> Key The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any <code>sb-scope</code> prefix. ```csharp [HtmlAttributeName("key")] public string? Key { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ## Methods ### <a id="HCI_SunBird_Web_SbHtmlTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbIconTagHelper.html --> # <a id="HCI_SunBird_Web_SbIconTagHelper"></a> Class SbIconTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll An editable icon from Google's Material Symbols (Outlined): <code><sb-icon key="service-icon" name="home" class="feature-icon" /></code>. Renders an inline <code><svg></code> that keeps every attribute other than <code>key</code> and <code>name</code>. It is 1em square and drawn in <code>currentColor</code>, so CSS sizes and colours it like text. It is hidden from screen readers unless the tag has an <code>aria-label</code>, when it is announced as an image. Editors choose another icon from a searchable library. Without a saved value, or when the saved icon is no longer in the library, it shows the icon named in <code>name</code>. ```csharp [HtmlTargetElement("sb-icon")] public sealed class SbIconTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbIconTagHelper](HCI.SunBird.Web.SbIconTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbIconTagHelper_Key"></a> Key The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any <code>sb-scope</code> prefix. ```csharp [HtmlAttributeName("key")] public string? Key { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ### <a id="HCI_SunBird_Web_SbIconTagHelper_Name"></a> Name The default icon's name in Material Symbols, such as <code>home</code> or <code>call</code>, shown until an editor chooses another. Names are listed at https://fonts.google.com/icons. ```csharp [HtmlAttributeName("name")] public string? Name { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ## Methods ### <a id="HCI_SunBird_Web_SbIconTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbImageTagHelper.html --> # <a id="HCI_SunBird_Web_SbImageTagHelper"></a> Class SbImageTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll An editable image slot: <code><sb-image key="hero-image" src="/img/hero.jpg" alt="Our office" /></code>. Renders an <code>img</code> with the slot's published image and alt text, or else its own <code>src</code> and <code>alt</code>. Every other attribute, such as <code>class</code>, <code>width</code> or <code>loading</code>, is kept. ```csharp [HtmlTargetElement("sb-image", TagStructure = TagStructure.WithoutEndTag)] public sealed class SbImageTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbImageTagHelper](HCI.SunBird.Web.SbImageTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbImageTagHelper_Alt"></a> Alt The default alt text, shown with the default image. Empty marks a decorative image. ```csharp [HtmlAttributeName("alt")] public string? Alt { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ### <a id="HCI_SunBird_Web_SbImageTagHelper_Key"></a> Key The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any <code>sb-scope</code> prefix. ```csharp [HtmlAttributeName("key")] public string? Key { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ### <a id="HCI_SunBird_Web_SbImageTagHelper_Src"></a> Src The default image URL, shown until an editor publishes an image. ```csharp [HtmlAttributeName("src")] public string? Src { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ## Methods ### <a id="HCI_SunBird_Web_SbImageTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbLinkTagHelper.html --> # <a id="HCI_SunBird_Web_SbLinkTagHelper"></a> Class SbLinkTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll An editable link, such as a call-to-action button: <code><sb-link key="cta" href="/contact" class="button">Get a quote</sb-link></code>. Renders an <code><a></code> that keeps every attribute other than <code>key</code> and <code>href</code>. Editors change its text and choose where it goes: a page on the site, followed if its address changes, or an address they type. Without a saved value, or when the chosen page is not public, the link uses its own <code>href</code> and content. ```csharp [HtmlTargetElement("sb-link")] public sealed class SbLinkTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbLinkTagHelper](HCI.SunBird.Web.SbLinkTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbLinkTagHelper_Href"></a> Href The default address, used until an editor chooses another, or when the chosen page is not public. ```csharp [HtmlAttributeName("href")] public string? Href { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ### <a id="HCI_SunBird_Web_SbLinkTagHelper_Key"></a> Key The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any <code>sb-scope</code> prefix. ```csharp [HtmlAttributeName("key")] public string? Key { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ## Methods ### <a id="HCI_SunBird_Web_SbLinkTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbRepeaterTagHelper.html --> # <a id="HCI_SunBird_Web_SbRepeaterTagHelper"></a> Class SbRepeaterTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Repeats its content once per entry, and lets editors add, remove and reorder the entries: <code><sb-repeater key="gallery" count="3"><figure><sb-image key="picture" … /></figure></sb-repeater></code>. The slots inside each entry are stored under the repeater's key and the entry's number, such as <code>gallery.2.picture</code>. The tag itself is not rendered, so each entry's markup sits directly in its parent and CSS such as <code>:nth-child(even)</code> styles alternate entries. Repeaters cannot be nested. ```csharp [HtmlTargetElement("sb-repeater")] public sealed class SbRepeaterTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbRepeaterTagHelper](HCI.SunBird.Web.SbRepeaterTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbRepeaterTagHelper_Count"></a> Count How many entries a page shows until an editor changes them. Defaults to 1. ```csharp [HtmlAttributeName("count")] public int? Count { get; set; } ``` #### Property Value [int](https://learn.microsoft.com/dotnet/api/system.int32)? ### <a id="HCI_SunBird_Web_SbRepeaterTagHelper_Key"></a> Key The repeater's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any <code>sb-scope</code> prefix. The keys of the slots inside, with the entry number, must also fit in 100 characters. ```csharp [HtmlAttributeName("key")] public string? Key { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ### <a id="HCI_SunBird_Web_SbRepeaterTagHelper_Max"></a> Max The most entries an editor can add. Defaults to no limit. ```csharp [HtmlAttributeName("max")] public int? Max { get; set; } ``` #### Property Value [int](https://learn.microsoft.com/dotnet/api/system.int32)? ## Methods ### <a id="HCI_SunBird_Web_SbRepeaterTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbScopeTagHelper.html --> # <a id="HCI_SunBird_Web_SbScopeTagHelper"></a> Class SbScopeTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Prefixes the keys of the slots inside it, so a partial can be used more than once on a page: <code><sb-scope key="top"><partial name="_Hero" /></sb-scope></code> stores the partial's <code>hero-title</code> slot as <code>top.hero-title</code>. Scopes can nest. The tag itself is not rendered. ```csharp [HtmlTargetElement("sb-scope")] public sealed class SbScopeTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbScopeTagHelper](HCI.SunBird.Web.SbScopeTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbScopeTagHelper_Key"></a> Key The prefix for the keys inside, with the same rules as slot keys. ```csharp [HtmlAttributeName("key")] public string? Key { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ## Methods ### <a id="HCI_SunBird_Web_SbScopeTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbSeoTagHelper.html --> # <a id="HCI_SunBird_Web_SbSeoTagHelper"></a> Class SbSeoTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Renders the page's <code><title></code>, meta description, canonical link, Open Graph tags and <code>twitter:card</code>. Put <code><sb-seo /></code> in the layout's <code><head></code>, in place of the <code><title></code>. It shows the values an editor saved in the page settings, or else the defaults from <xref href="HCI.SunBird.Web.SunBirdPageAttribute.Title" data-throw-if-not-resolved="false"></xref> and <xref href="HCI.SunBird.Web.SunBirdPageAttribute.Description" data-throw-if-not-resolved="false"></xref>. On a page without <xref href="HCI.SunBird.Web.SunBirdPageAttribute" data-throw-if-not-resolved="false"></xref> it renders nothing. ```csharp [HtmlTargetElement("sb-seo", TagStructure = TagStructure.WithoutEndTag)] public sealed class SbSeoTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbSeoTagHelper](HCI.SunBird.Web.SbSeoTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Methods ### <a id="HCI_SunBird_Web_SbSeoTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbSubPagesTagHelper.html --> # <a id="HCI_SunBird_Web_SbSubPagesTagHelper"></a> Class SbSubPagesTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Lists the published pages editors added under a page, newest first, as links: <code><sb-sub-pages /></code> renders <code><ul><li><a href="/news/opening-day">Opening day</a></li></ul></code> and keeps any other attributes, such as <code>class</code>, on the <code><ul></code>. It renders nothing when there are none. For other markup, use <xref href="HCI.SunBird.Web.ISunBirdPages" data-throw-if-not-resolved="false"></xref>. ```csharp [HtmlTargetElement("sb-sub-pages", TagStructure = TagStructure.WithoutEndTag)] public sealed class SbSubPagesTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbSubPagesTagHelper](HCI.SunBird.Web.SbSubPagesTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbSubPagesTagHelper_Limit"></a> Limit The most pages to list, such as 3 for the latest news on the home page. Defaults to all. ```csharp [HtmlAttributeName("limit")] public int? Limit { get; set; } ``` #### Property Value [int](https://learn.microsoft.com/dotnet/api/system.int32)? ### <a id="HCI_SunBird_Web_SbSubPagesTagHelper_PageKey"></a> PageKey The key of the page whose sub-pages to list. Defaults to the page being shown. ```csharp [HtmlAttributeName("page")] public string? PageKey { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ## Methods ### <a id="HCI_SunBird_Web_SbSubPagesTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbTableTagHelper.html --> # <a id="HCI_SunBird_Web_SbTableTagHelper"></a> Class SbTableTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll An editable table, such as a price list: <code><sb-table key="prices" class="price-list"></code> with a <code><thead></code> and one or more <code><tbody></code> sections inside it. Renders a <code><table></code> that keeps every attribute other than <code>key</code>. Without a saved value it shows its own content, unchanged. Editors change the text of every cell, and add, remove and move columns, rows and sections. A saved table is written as plain HTML with no classes or styles of its own: column headings in <code><thead></code> as <code><th scope="col"></code>, each section a <code><tbody></code> whose optional heading row is one <code><th scope="rowgroup"></code> across every column, and the first cell of each row a <code><th scope="row"></code>. ```csharp [HtmlTargetElement("sb-table")] public sealed class SbTableTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbTableTagHelper](HCI.SunBird.Web.SbTableTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbTableTagHelper_Key"></a> Key The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any <code>sb-scope</code> prefix. ```csharp [HtmlAttributeName("key")] public string? Key { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ## Methods ### <a id="HCI_SunBird_Web_SbTableTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SbTextTagHelper.html --> # <a id="HCI_SunBird_Web_SbTextTagHelper"></a> Class SbTextTagHelper Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll An editable plain-text slot: <code><sb-text key="hero-title">Default text</sb-text></code>. Renders the slot's published text, HTML-encoded, or else its own content. The tag itself is not rendered. ```csharp [HtmlTargetElement("sb-text")] public sealed class SbTextTagHelper : TagHelper, ITagHelper, ITagHelperComponent ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [TagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper) ← [SbTextTagHelper](HCI.SunBird.Web.SbTextTagHelper.md) #### Implements [ITagHelper](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelper), [ITagHelperComponent](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.itaghelpercomponent) #### Inherited Members [TagHelper.Init\(TagHelperContext\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.init), [TagHelper.Process\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.process), [TagHelper.ProcessAsync\(TagHelperContext, TagHelperOutput\)](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.processasync), [TagHelper.Order](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelper.order), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SbTextTagHelper_Key"></a> Key The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any <code>sb-scope</code> prefix. ```csharp [HtmlAttributeName("key")] public string? Key { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ## Methods ### <a id="HCI_SunBird_Web_SbTextTagHelper_ProcessAsync_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperContext_Microsoft_AspNetCore_Razor_TagHelpers_TagHelperOutput_"></a> ProcessAsync\(TagHelperContext, TagHelperOutput\) Asynchronously executes the <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper" data-throw-if-not-resolved="false"></xref> with the given <code class="paramref">context</code> and <code class="paramref">output</code>. ```csharp public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) ``` #### Parameters `context` [TagHelperContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelpercontext) Contains information associated with the current HTML tag. `output` [TagHelperOutput](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.razor.taghelpers.taghelperoutput) A stateful HTML element used to generate an HTML tag. #### Returns [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task) A <xref href="System.Threading.Tasks.Task" data-throw-if-not-resolved="false"></xref> that on completion updates the <code class="paramref">output</code>. #### Remarks By default this calls into <xref href="Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext%2cMicrosoft.AspNetCore.Razor.TagHelpers.TagHelperOutput)" data-throw-if-not-resolved="false"></xref>. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SunBirdEndpointRouteBuilderExtensions.html --> # <a id="HCI_SunBird_Web_SunBirdEndpointRouteBuilderExtensions"></a> Class SunBirdEndpointRouteBuilderExtensions Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Wires SunBird into a host site's request pipeline. ```csharp public static class SunBirdEndpointRouteBuilderExtensions ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [SunBirdEndpointRouteBuilderExtensions](HCI.SunBird.Web.SunBirdEndpointRouteBuilderExtensions.md) #### Inherited Members [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.MemberwiseClone\(\)](https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Methods ### <a id="HCI_SunBird_Web_SunBirdEndpointRouteBuilderExtensions_MapSunBird_Microsoft_AspNetCore_Routing_IEndpointRouteBuilder_"></a> MapSunBird\(IEndpointRouteBuilder\) Applies any pending SunBird database migrations, then maps SunBird's endpoints, including the route that serves pages editors create from templates (<xref href="HCI.SunBird.Web.SunBirdTemplateAttribute" data-throw-if-not-resolved="false"></xref>) at their addresses. Code pages always win over those. Call it once at startup, after <code>builder.Build()</code>. ```csharp public static IEndpointRouteBuilder MapSunBird(this IEndpointRouteBuilder endpoints) ``` #### Parameters `endpoints` [IEndpointRouteBuilder](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.routing.iendpointroutebuilder) The host site's application. #### Returns [IEndpointRouteBuilder](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.routing.iendpointroutebuilder) The same <code class="paramref">endpoints</code>, for chaining. #### Exceptions [InvalidOperationException](https://learn.microsoft.com/dotnet/api/system.invalidoperationexception) No database was configured on <xref href="HCI.SunBird.Web.ISunBirdBuilder" data-throw-if-not-resolved="false"></xref>, no HCI.Email <code>IEmailService</code> or HCI.FileStorage <code>IFileStorageService</code> is registered, <code>SunBird:Email:From</code> or <code>SunBird:SiteUrl</code> is not set, Razor Pages is not added, or the <xref href="HCI.SunBird.Web.SunBirdPageAttribute" data-throw-if-not-resolved="false"></xref> and <xref href="HCI.SunBird.Web.SunBirdTemplateAttribute" data-throw-if-not-resolved="false"></xref> attributes break a rule (a repeated template key, an unknown key in <code>ChildTemplates</code>, or sub-pages under a page whose route has parameters). --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SunBirdFragmentAttribute.html --> # <a id="HCI_SunBird_Web_SunBirdFragmentAttribute"></a> Class SunBirdFragmentAttribute Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Makes a partial view a fragment: content shared by every page that shows it, such as testimonials, edited once. Put it in the partial with <code>@attribute [HCI.SunBird.Web.SunBirdFragment("testimonials")]</code>, and show the partial with <code><sb-fragment name="_Testimonials" /></code>. The partial's markup is the fragment's default. ```csharp [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class SunBirdFragmentAttribute : Attribute ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [Attribute](https://learn.microsoft.com/dotnet/api/system.attribute) ← [SunBirdFragmentAttribute](HCI.SunBird.Web.SunBirdFragmentAttribute.md) #### Inherited Members [Attribute.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.attribute.equals), [Attribute.GetCustomAttribute\(Assembly, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-assembly\-system\-type\)), [Attribute.GetCustomAttribute\(Assembly, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-assembly\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttribute\(MemberInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-memberinfo\-system\-type\)), [Attribute.GetCustomAttribute\(MemberInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-memberinfo\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttribute\(Module, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-module\-system\-type\)), [Attribute.GetCustomAttribute\(Module, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-module\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttribute\(ParameterInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-parameterinfo\-system\-type\)), [Attribute.GetCustomAttribute\(ParameterInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-parameterinfo\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(Assembly\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\)), [Attribute.GetCustomAttributes\(Assembly, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\-system\-boolean\)), [Attribute.GetCustomAttributes\(Assembly, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\-system\-type\)), [Attribute.GetCustomAttributes\(Assembly, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(MemberInfo\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\)), [Attribute.GetCustomAttributes\(MemberInfo, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\-system\-boolean\)), [Attribute.GetCustomAttributes\(MemberInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\-system\-type\)), [Attribute.GetCustomAttributes\(MemberInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(Module\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\)), [Attribute.GetCustomAttributes\(Module, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\-system\-boolean\)), [Attribute.GetCustomAttributes\(Module, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\-system\-type\)), [Attribute.GetCustomAttributes\(Module, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(ParameterInfo\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\)), [Attribute.GetCustomAttributes\(ParameterInfo, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\-system\-boolean\)), [Attribute.GetCustomAttributes\(ParameterInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\-system\-type\)), [Attribute.GetCustomAttributes\(ParameterInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\-system\-type\-system\-boolean\)), [Attribute.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.attribute.gethashcode), [Attribute.IsDefaultAttribute\(\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefaultattribute), [Attribute.IsDefined\(Assembly, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-assembly\-system\-type\)), [Attribute.IsDefined\(Assembly, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-assembly\-system\-type\-system\-boolean\)), [Attribute.IsDefined\(MemberInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-memberinfo\-system\-type\)), [Attribute.IsDefined\(MemberInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-memberinfo\-system\-type\-system\-boolean\)), [Attribute.IsDefined\(Module, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-module\-system\-type\)), [Attribute.IsDefined\(Module, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-module\-system\-type\-system\-boolean\)), [Attribute.IsDefined\(ParameterInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-parameterinfo\-system\-type\)), [Attribute.IsDefined\(ParameterInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-parameterinfo\-system\-type\-system\-boolean\)), [Attribute.Match\(object?\)](https://learn.microsoft.com/dotnet/api/system.attribute.match), [Attribute.TypeId](https://learn.microsoft.com/dotnet/api/system.attribute.typeid), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Constructors ### <a id="HCI_SunBird_Web_SunBirdFragmentAttribute__ctor_System_String_"></a> SunBirdFragmentAttribute\(string\) Creates the attribute. ```csharp public SunBirdFragmentAttribute(string key) ``` #### Parameters `key` [string](https://learn.microsoft.com/dotnet/api/system.string) The fragment's key, which its content is stored under: 1 to 100 ASCII letters, digits, '-', '_' or '.', unique among the site's fragments. Changing it loses the fragment's content. ## Properties ### <a id="HCI_SunBird_Web_SunBirdFragmentAttribute_Key"></a> Key The fragment's key. ```csharp public string Key { get; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string) --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SunBirdHostBuilderExtensions.html --> # <a id="HCI_SunBird_Web_SunBirdHostBuilderExtensions"></a> Class SunBirdHostBuilderExtensions Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Adds SunBird to a host site. ```csharp public static class SunBirdHostBuilderExtensions ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [SunBirdHostBuilderExtensions](HCI.SunBird.Web.SunBirdHostBuilderExtensions.md) #### Inherited Members [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.MemberwiseClone\(\)](https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Methods ### <a id="HCI_SunBird_Web_SunBirdHostBuilderExtensions_AddSunBird_Microsoft_Extensions_Hosting_IHostApplicationBuilder_"></a> AddSunBird\(IHostApplicationBuilder\) Registers SunBird's services. Chain a data package's method on the result to choose the database, for example <code>builder.AddSunBird().UseSqlServer(connectionString)</code>. ```csharp public static ISunBirdBuilder AddSunBird(this IHostApplicationBuilder builder) ``` #### Parameters `builder` [IHostApplicationBuilder](https://learn.microsoft.com/dotnet/api/microsoft.extensions.hosting.ihostapplicationbuilder) The host site's application builder. #### Returns [ISunBirdBuilder](HCI.SunBird.Web.ISunBirdBuilder.md) A builder for configuring SunBird further. #### Remarks SunBird's sign-in becomes the site's sign-in: this sets up ASP.NET Identity with SunBird's editor accounts as the default authentication, and adds the sign-in and account pages under <code>/sunbird/</code>. The site needs <code>AddRazorPages()</code> and <code>MapRazorPages()</code> for those pages. Create the first admin at <code>/sunbird/setup</code> with the code set in <code>SunBird:SetupCode</code>. SunBird emails invites and password resets through the site's HCI.Email <code>IEmailService</code>, so register one and set <code>SunBird:Email:From</code> and <code>SunBird:SiteUrl</code>. Editors' images are kept through the site's HCI.FileStorage <code>IFileStorageService</code>, so register one of those too. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SunBirdPageAttribute.html --> # <a id="HCI_SunBird_Web_SunBirdPageAttribute"></a> Class SunBirdPageAttribute Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Makes a Razor page editable with SunBird, under a stable page key. Put it on the page's PageModel: <code>[SunBirdPage("home")]</code>. The key is an opaque identifier, not the URL, so the page can move without losing its content. ```csharp [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class SunBirdPageAttribute : Attribute ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [Attribute](https://learn.microsoft.com/dotnet/api/system.attribute) ← [SunBirdPageAttribute](HCI.SunBird.Web.SunBirdPageAttribute.md) #### Inherited Members [Attribute.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.attribute.equals), [Attribute.GetCustomAttribute\(Assembly, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-assembly\-system\-type\)), [Attribute.GetCustomAttribute\(Assembly, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-assembly\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttribute\(MemberInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-memberinfo\-system\-type\)), [Attribute.GetCustomAttribute\(MemberInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-memberinfo\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttribute\(Module, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-module\-system\-type\)), [Attribute.GetCustomAttribute\(Module, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-module\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttribute\(ParameterInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-parameterinfo\-system\-type\)), [Attribute.GetCustomAttribute\(ParameterInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-parameterinfo\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(Assembly\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\)), [Attribute.GetCustomAttributes\(Assembly, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\-system\-boolean\)), [Attribute.GetCustomAttributes\(Assembly, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\-system\-type\)), [Attribute.GetCustomAttributes\(Assembly, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(MemberInfo\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\)), [Attribute.GetCustomAttributes\(MemberInfo, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\-system\-boolean\)), [Attribute.GetCustomAttributes\(MemberInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\-system\-type\)), [Attribute.GetCustomAttributes\(MemberInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(Module\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\)), [Attribute.GetCustomAttributes\(Module, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\-system\-boolean\)), [Attribute.GetCustomAttributes\(Module, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\-system\-type\)), [Attribute.GetCustomAttributes\(Module, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(ParameterInfo\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\)), [Attribute.GetCustomAttributes\(ParameterInfo, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\-system\-boolean\)), [Attribute.GetCustomAttributes\(ParameterInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\-system\-type\)), [Attribute.GetCustomAttributes\(ParameterInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\-system\-type\-system\-boolean\)), [Attribute.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.attribute.gethashcode), [Attribute.IsDefaultAttribute\(\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefaultattribute), [Attribute.IsDefined\(Assembly, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-assembly\-system\-type\)), [Attribute.IsDefined\(Assembly, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-assembly\-system\-type\-system\-boolean\)), [Attribute.IsDefined\(MemberInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-memberinfo\-system\-type\)), [Attribute.IsDefined\(MemberInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-memberinfo\-system\-type\-system\-boolean\)), [Attribute.IsDefined\(Module, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-module\-system\-type\)), [Attribute.IsDefined\(Module, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-module\-system\-type\-system\-boolean\)), [Attribute.IsDefined\(ParameterInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-parameterinfo\-system\-type\)), [Attribute.IsDefined\(ParameterInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-parameterinfo\-system\-type\-system\-boolean\)), [Attribute.Match\(object?\)](https://learn.microsoft.com/dotnet/api/system.attribute.match), [Attribute.TypeId](https://learn.microsoft.com/dotnet/api/system.attribute.typeid), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Constructors ### <a id="HCI_SunBird_Web_SunBirdPageAttribute__ctor_System_String_"></a> SunBirdPageAttribute\(string\) Marks the page with the given key. ```csharp public SunBirdPageAttribute(string key) ``` #### Parameters `key` [string](https://learn.microsoft.com/dotnet/api/system.string) The page key: 1 to 100 ASCII letters, digits, '-', '_' or '.', unique on the site. ## Properties ### <a id="HCI_SunBird_Web_SunBirdPageAttribute_ChildTemplates"></a> ChildTemplates The keys of the templates (<xref href="HCI.SunBird.Web.SunBirdTemplateAttribute" data-throw-if-not-resolved="false"></xref>) editors may add pages from under this page, such as <code>ChildTemplates = ["article"]</code>. When set, the editor bar shows <b>New page</b> here, and each new page's address is this page's address plus a slug. Only a page whose route has no parameters can have sub-pages. ```csharp public string[]? ChildTemplates { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)\[\]? ### <a id="HCI_SunBird_Web_SunBirdPageAttribute_Description"></a> Description The page's default meta description, rendered by <code><sb-seo /></code>. An editor can override it in the page settings. At most 500 characters. ```csharp public string? Description { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ### <a id="HCI_SunBird_Web_SunBirdPageAttribute_Key"></a> Key The page key, under which SunBird stores the page's content. ```csharp public string Key { get; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string) ### <a id="HCI_SunBird_Web_SunBirdPageAttribute_Title"></a> Title The page's default title for search engines and sharing, rendered by <code><sb-seo /></code> as the <code><title></code>. An editor can override it in the page settings. At most 200 characters. ```csharp public string? Title { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SunBirdPageLink.html --> # <a id="HCI_SunBird_Web_SunBirdPageLink"></a> Class SunBirdPageLink Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll A published page that an editor created from a template, as <xref href="HCI.SunBird.Web.ISunBirdPages" data-throw-if-not-resolved="false"></xref> lists it. ```csharp public sealed class SunBirdPageLink ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [SunBirdPageLink](HCI.SunBird.Web.SunBirdPageLink.md) #### Inherited Members [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Properties ### <a id="HCI_SunBird_Web_SunBirdPageLink_FirstPublishedUtc"></a> FirstPublishedUtc When the page was first published, in UTC. ```csharp public DateTimeOffset FirstPublishedUtc { get; } ``` #### Property Value [DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset) ### <a id="HCI_SunBird_Web_SunBirdPageLink_Key"></a> Key The page's key, to list its own sub-pages in turn. ```csharp public string Key { get; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string) ### <a id="HCI_SunBird_Web_SunBirdPageLink_Title"></a> Title The page's published title. ```csharp public string Title { get; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string) ### <a id="HCI_SunBird_Web_SunBirdPageLink_Url"></a> Url The page's address from the site root, such as <code>/news/opening-day</code>, including any path base. ```csharp public string Url { get; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string) --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.SunBirdTemplateAttribute.html --> # <a id="HCI_SunBird_Web_SunBirdTemplateAttribute"></a> Class SunBirdTemplateAttribute Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md) Assembly: HCI.SunBird.dll Makes a Razor page a template that editors can create new pages from, such as a news article. Put it on the page's PageModel instead of <xref href="HCI.SunBird.Web.SunBirdPageAttribute" data-throw-if-not-resolved="false"></xref>: <code>[SunBirdTemplate("article", Name = "Article")]</code>, and list its key in <xref href="HCI.SunBird.Web.SunBirdPageAttribute.ChildTemplates" data-throw-if-not-resolved="false"></xref> on the pages it may be added under. Each page an editor creates from it has its own title, address and slot content, and is drafted, published and rolled back like any other page. The template's own address answers 404; it is reached only through the pages made from it. ```csharp [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class SunBirdTemplateAttribute : Attribute ``` #### Inheritance [object](https://learn.microsoft.com/dotnet/api/system.object) ← [Attribute](https://learn.microsoft.com/dotnet/api/system.attribute) ← [SunBirdTemplateAttribute](HCI.SunBird.Web.SunBirdTemplateAttribute.md) #### Inherited Members [Attribute.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.attribute.equals), [Attribute.GetCustomAttribute\(Assembly, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-assembly\-system\-type\)), [Attribute.GetCustomAttribute\(Assembly, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-assembly\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttribute\(MemberInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-memberinfo\-system\-type\)), [Attribute.GetCustomAttribute\(MemberInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-memberinfo\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttribute\(Module, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-module\-system\-type\)), [Attribute.GetCustomAttribute\(Module, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-module\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttribute\(ParameterInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-parameterinfo\-system\-type\)), [Attribute.GetCustomAttribute\(ParameterInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattribute\#system\-attribute\-getcustomattribute\(system\-reflection\-parameterinfo\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(Assembly\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\)), [Attribute.GetCustomAttributes\(Assembly, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\-system\-boolean\)), [Attribute.GetCustomAttributes\(Assembly, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\-system\-type\)), [Attribute.GetCustomAttributes\(Assembly, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-assembly\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(MemberInfo\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\)), [Attribute.GetCustomAttributes\(MemberInfo, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\-system\-boolean\)), [Attribute.GetCustomAttributes\(MemberInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\-system\-type\)), [Attribute.GetCustomAttributes\(MemberInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-memberinfo\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(Module\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\)), [Attribute.GetCustomAttributes\(Module, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\-system\-boolean\)), [Attribute.GetCustomAttributes\(Module, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\-system\-type\)), [Attribute.GetCustomAttributes\(Module, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-module\-system\-type\-system\-boolean\)), [Attribute.GetCustomAttributes\(ParameterInfo\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\)), [Attribute.GetCustomAttributes\(ParameterInfo, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\-system\-boolean\)), [Attribute.GetCustomAttributes\(ParameterInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\-system\-type\)), [Attribute.GetCustomAttributes\(ParameterInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.getcustomattributes\#system\-attribute\-getcustomattributes\(system\-reflection\-parameterinfo\-system\-type\-system\-boolean\)), [Attribute.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.attribute.gethashcode), [Attribute.IsDefaultAttribute\(\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefaultattribute), [Attribute.IsDefined\(Assembly, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-assembly\-system\-type\)), [Attribute.IsDefined\(Assembly, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-assembly\-system\-type\-system\-boolean\)), [Attribute.IsDefined\(MemberInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-memberinfo\-system\-type\)), [Attribute.IsDefined\(MemberInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-memberinfo\-system\-type\-system\-boolean\)), [Attribute.IsDefined\(Module, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-module\-system\-type\)), [Attribute.IsDefined\(Module, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-module\-system\-type\-system\-boolean\)), [Attribute.IsDefined\(ParameterInfo, Type\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-parameterinfo\-system\-type\)), [Attribute.IsDefined\(ParameterInfo, Type, bool\)](https://learn.microsoft.com/dotnet/api/system.attribute.isdefined\#system\-attribute\-isdefined\(system\-reflection\-parameterinfo\-system\-type\-system\-boolean\)), [Attribute.Match\(object?\)](https://learn.microsoft.com/dotnet/api/system.attribute.match), [Attribute.TypeId](https://learn.microsoft.com/dotnet/api/system.attribute.typeid), [object.Equals\(object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\)), [object.Equals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.equals\#system\-object\-equals\(system\-object\-system\-object\)), [object.GetHashCode\(\)](https://learn.microsoft.com/dotnet/api/system.object.gethashcode), [object.GetType\(\)](https://learn.microsoft.com/dotnet/api/system.object.gettype), [object.ReferenceEquals\(object?, object?\)](https://learn.microsoft.com/dotnet/api/system.object.referenceequals), [object.ToString\(\)](https://learn.microsoft.com/dotnet/api/system.object.tostring) ## Constructors ### <a id="HCI_SunBird_Web_SunBirdTemplateAttribute__ctor_System_String_"></a> SunBirdTemplateAttribute\(string\) Marks the page as the template with the given key. ```csharp public SunBirdTemplateAttribute(string key) ``` #### Parameters `key` [string](https://learn.microsoft.com/dotnet/api/system.string) The template key: 1 to 100 ASCII letters, digits, '-', '_' or '.', unique on the site. Pages made from the template keep it, so do not change it once editors have used the template. ## Properties ### <a id="HCI_SunBird_Web_SunBirdTemplateAttribute_ChildTemplates"></a> ChildTemplates The keys of the templates editors may add pages from under each page made from this template, so a section can have its own pages. Leave it unset for pages that have none. ```csharp public string[]? ChildTemplates { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)\[\]? ### <a id="HCI_SunBird_Web_SunBirdTemplateAttribute_Description"></a> Description An optional sentence shown under the name, saying what the template is for. ```csharp public string? Description { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? ### <a id="HCI_SunBird_Web_SunBirdTemplateAttribute_Key"></a> Key The template key, stored with every page made from the template. ```csharp public string Key { get; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string) ### <a id="HCI_SunBird_Web_SunBirdTemplateAttribute_Name"></a> Name The name editors see when they choose a template, such as "Article". Defaults to the key. ```csharp public string? Name { get; set; } ``` #### Property Value [string](https://learn.microsoft.com/dotnet/api/system.string)? --- <!-- Source: https://sunbird-doc.hciyork.co.uk/api/HCI.SunBird.Web.html --> # <a id="HCI_SunBird_Web"></a> Namespace HCI.SunBird.Web ### Classes [SbFileTagHelper](HCI.SunBird.Web.SbFileTagHelper.md) An editable download link, such as a form or leaflet: <code><sb-file key="referral-form" href="/docs/referral-form.pdf" show-size>Referral form</sb-file></code>. Renders an <code><a></code> that keeps every attribute other than <code>key</code>, <code>href</code> and <code>show-size</code>. Editors change its text and choose the PDF it gives from the media library. Without a saved value the link uses its own <code>href</code> and content. [SbFragmentTagHelper](HCI.SunBird.Web.SbFragmentTagHelper.md) Shows a fragment: a partial view with <xref href="HCI.SunBird.Web.SunBirdFragmentAttribute" data-throw-if-not-resolved="false"></xref>, whose content is shared by every page that shows it: <code><sb-fragment name="_Testimonials" /></code>. The slots inside belong to the fragment, not the page, so an editor changes them once for every page. It can be used on pages, templates and layouts. The tag itself is not rendered. [SbHtmlTagHelper](HCI.SunBird.Web.SbHtmlTagHelper.md) An editable rich-text slot: <code><sb-html key="intro"><p>Default</p></sb-html></code>. Renders the slot's published HTML, which SunBird wrote from an HCI.RichText document when it was saved, or else its own content. The tag itself is not rendered. [SbIconTagHelper](HCI.SunBird.Web.SbIconTagHelper.md) An editable icon from Google's Material Symbols (Outlined): <code><sb-icon key="service-icon" name="home" class="feature-icon" /></code>. Renders an inline <code><svg></code> that keeps every attribute other than <code>key</code> and <code>name</code>. It is 1em square and drawn in <code>currentColor</code>, so CSS sizes and colours it like text. It is hidden from screen readers unless the tag has an <code>aria-label</code>, when it is announced as an image. Editors choose another icon from a searchable library. Without a saved value, or when the saved icon is no longer in the library, it shows the icon named in <code>name</code>. [SbImageTagHelper](HCI.SunBird.Web.SbImageTagHelper.md) An editable image slot: <code><sb-image key="hero-image" src="/img/hero.jpg" alt="Our office" /></code>. Renders an <code>img</code> with the slot's published image and alt text, or else its own <code>src</code> and <code>alt</code>. Every other attribute, such as <code>class</code>, <code>width</code> or <code>loading</code>, is kept. [SbLinkTagHelper](HCI.SunBird.Web.SbLinkTagHelper.md) An editable link, such as a call-to-action button: <code><sb-link key="cta" href="/contact" class="button">Get a quote</sb-link></code>. Renders an <code><a></code> that keeps every attribute other than <code>key</code> and <code>href</code>. Editors change its text and choose where it goes: a page on the site, followed if its address changes, or an address they type. Without a saved value, or when the chosen page is not public, the link uses its own <code>href</code> and content. [SbRepeaterTagHelper](HCI.SunBird.Web.SbRepeaterTagHelper.md) Repeats its content once per entry, and lets editors add, remove and reorder the entries: <code><sb-repeater key="gallery" count="3"><figure><sb-image key="picture" … /></figure></sb-repeater></code>. The slots inside each entry are stored under the repeater's key and the entry's number, such as <code>gallery.2.picture</code>. The tag itself is not rendered, so each entry's markup sits directly in its parent and CSS such as <code>:nth-child(even)</code> styles alternate entries. Repeaters cannot be nested. [SbScopeTagHelper](HCI.SunBird.Web.SbScopeTagHelper.md) Prefixes the keys of the slots inside it, so a partial can be used more than once on a page: <code><sb-scope key="top"><partial name="_Hero" /></sb-scope></code> stores the partial's <code>hero-title</code> slot as <code>top.hero-title</code>. Scopes can nest. The tag itself is not rendered. [SbSeoTagHelper](HCI.SunBird.Web.SbSeoTagHelper.md) Renders the page's <code><title></code>, meta description, canonical link, Open Graph tags and <code>twitter:card</code>. Put <code><sb-seo /></code> in the layout's <code><head></code>, in place of the <code><title></code>. It shows the values an editor saved in the page settings, or else the defaults from <xref href="HCI.SunBird.Web.SunBirdPageAttribute.Title" data-throw-if-not-resolved="false"></xref> and <xref href="HCI.SunBird.Web.SunBirdPageAttribute.Description" data-throw-if-not-resolved="false"></xref>. On a page without <xref href="HCI.SunBird.Web.SunBirdPageAttribute" data-throw-if-not-resolved="false"></xref> it renders nothing. [SbSubPagesTagHelper](HCI.SunBird.Web.SbSubPagesTagHelper.md) Lists the published pages editors added under a page, newest first, as links: <code><sb-sub-pages /></code> renders <code><ul><li><a href="/news/opening-day">Opening day</a></li></ul></code> and keeps any other attributes, such as <code>class</code>, on the <code><ul></code>. It renders nothing when there are none. For other markup, use <xref href="HCI.SunBird.Web.ISunBirdPages" data-throw-if-not-resolved="false"></xref>. [SbTableTagHelper](HCI.SunBird.Web.SbTableTagHelper.md) An editable table, such as a price list: <code><sb-table key="prices" class="price-list"></code> with a <code><thead></code> and one or more <code><tbody></code> sections inside it. Renders a <code><table></code> that keeps every attribute other than <code>key</code>. Without a saved value it shows its own content, unchanged. Editors change the text of every cell, and add, remove and move columns, rows and sections. A saved table is written as plain HTML with no classes or styles of its own: column headings in <code><thead></code> as <code><th scope="col"></code>, each section a <code><tbody></code> whose optional heading row is one <code><th scope="rowgroup"></code> across every column, and the first cell of each row a <code><th scope="row"></code>. [SbTextTagHelper](HCI.SunBird.Web.SbTextTagHelper.md) An editable plain-text slot: <code><sb-text key="hero-title">Default text</sb-text></code>. Renders the slot's published text, HTML-encoded, or else its own content. The tag itself is not rendered. [SunBirdEndpointRouteBuilderExtensions](HCI.SunBird.Web.SunBirdEndpointRouteBuilderExtensions.md) Wires SunBird into a host site's request pipeline. [SunBirdFragmentAttribute](HCI.SunBird.Web.SunBirdFragmentAttribute.md) Makes a partial view a fragment: content shared by every page that shows it, such as testimonials, edited once. Put it in the partial with <code>@attribute [HCI.SunBird.Web.SunBirdFragment("testimonials")]</code>, and show the partial with <code><sb-fragment name="_Testimonials" /></code>. The partial's markup is the fragment's default. [SunBirdHostBuilderExtensions](HCI.SunBird.Web.SunBirdHostBuilderExtensions.md) Adds SunBird to a host site. [SunBirdPageAttribute](HCI.SunBird.Web.SunBirdPageAttribute.md) Makes a Razor page editable with SunBird, under a stable page key. Put it on the page's PageModel: <code>[SunBirdPage("home")]</code>. The key is an opaque identifier, not the URL, so the page can move without losing its content. [SunBirdPageLink](HCI.SunBird.Web.SunBirdPageLink.md) A published page that an editor created from a template, as <xref href="HCI.SunBird.Web.ISunBirdPages" data-throw-if-not-resolved="false"></xref> lists it. [SunBirdTemplateAttribute](HCI.SunBird.Web.SunBirdTemplateAttribute.md) Makes a Razor page a template that editors can create new pages from, such as a news article. Put it on the page's PageModel instead of <xref href="HCI.SunBird.Web.SunBirdPageAttribute" data-throw-if-not-resolved="false"></xref>: <code>[SunBirdTemplate("article", Name = "Article")]</code>, and list its key in <xref href="HCI.SunBird.Web.SunBirdPageAttribute.ChildTemplates" data-throw-if-not-resolved="false"></xref> on the pages it may be added under. Each page an editor creates from it has its own title, address and slot content, and is drafted, published and rolled back like any other page. The template's own address answers 404; it is reached only through the pages made from it. ### Interfaces [ISunBirdBuilder](HCI.SunBird.Web.ISunBirdBuilder.md) Configures SunBird after <xref href="HCI.SunBird.Web.SunBirdHostBuilderExtensions.AddSunBird(Microsoft.Extensions.Hosting.IHostApplicationBuilder)" data-throw-if-not-resolved="false"></xref>. A data package adds the extension method that chooses the database, such as <code>UseSqlServer(connectionString)</code> from <code>HCI.SunBird.Data.SqlServer</code>. [ISunBirdPages](HCI.SunBird.Web.ISunBirdPages.md) Reads the pages editors created from templates, for listings with markup of the site's own, such as news cards. Inject it into a view (<code>@inject ISunBirdPages Pages</code>) or a PageModel. For a plain list of links, use <code><sb-sub-pages /></code>. Reads are cached in memory, so a public page view never writes to the database. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/release-notes/index.html --> # Release notes The newest version is first. All three packages (`HCI.SunBird`, `HCI.SunBird.Data.SqlServer` and `HCI.SunBird.Data.Sqlite`) share one version, so upgrade them together. - [1.1.0](1-1-0.md): an MCP server for agents, `<sb-table>` and `<sb-icon>`, and a clearer edited-slot outline. --- <!-- Source: https://sunbird-doc.hciyork.co.uk/release-notes/1-1-0.html --> # SunBird 1.1.0 ## What is new - **MCP server.** An AI agent can now load content into a site through the same rules as edit mode, using an access token for an editor account. Every editor action has an MCP tool, and `search_icons` finds icons in the catalogue. See [Let agents edit content over MCP](../how-to/let-agents-edit-content.md). - **`<sb-table>`.** Editors can change a table's cells, rows, columns and sections in place, with Row, Column and Section tabs and add buttons. See [Make a page editable](../how-to/make-a-page-editable.md). - **`<sb-icon>`.** An icon chosen from an embedded, searchable Material Symbols library and drawn as inline SVG. Editors pick it in an icon rail. See [Make a page editable](../how-to/make-a-page-editable.md). - **Edited slots.** A slot with unsaved changes now has a thicker outline in place of a background tint, so it no longer changes how the content looks. ## Upgrading 1. Update all three packages to 1.1.0 together. 2. The release adds one migration, `AddMcpTokens`, to both the SQL Server and SQLite packages. It only adds a table, so it is safe while a staging and a production site share one database. See [Staging mode](../concepts/staging.md). 3. Nothing needs changing in existing pages. The MCP server is a separate, opt-in feature. ## New public API `SbTableTagHelper` and `SbIconTagHelper`, plus the MCP setup described in the MCP guide. ---