` | `th[scope="rowgroup"]` |
| A row's first cell | ` ` | `th[scope="row"]` |
| The other cells | ` ` | `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 ``, and
leaves out the ` ` 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 ``. 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
``, `` and fragments like any slot.
## Icons
`` 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
```
It renders the icon as an inline `` that keeps every attribute other than `key` and `name`:
```html
```
- **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 ``, `` and shared fragments like any slot.
## Titles and descriptions
Set the page's default title and meta description on the attribute, and put ` ` in the layout's ``
instead of a ``. 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 ``, 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
```
The partial uses plain keys:
```cshtml
@model int
@* One step of "How it works". The page wraps each use in , 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."),
};
}
@Model
@title
@body
```
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, `` 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) (``), 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: `` 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).
---
# 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 `` 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 `` 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: `` becomes a ``, `` a ``, and `
` keeps its ` ` 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 `` 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 ``) 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.
---
# 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 ``, 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
A caption for the picture
```
- `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 `` 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 ``, the scope's key comes first: `` around `` 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`.
---
# 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")]
What our clients say
A sentence from a happy client.
Client name
```
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
```
`name` finds the partial as ` ` 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 *@
```
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 `` 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)):
- `` 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).
---
# 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 `` 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).
---
# 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
A headline for the story
One or two sentences that sum the story up.
The story itself.
```
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 ` ` in the layout's ``, so each added page gets its title as its ``.
## 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
```
It renders the published sub-pages of the current page as links, newest first, and nothing if there are none:
```html
```
`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:** ``, so editors can choose which pages show in the site's navigation.
---
# 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();
```
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" ""` |
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`.
---
# 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.
---
# 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 ` `. 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 `` 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.
---
# 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.
---
# 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.
---
# 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 ` ` 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).
---
# 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.
---
# 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
### UseSqlServer\(ISunBirdBuilder, string\)
Stores SunBird content in the SQL Server database at connectionString. SunBird's tables go
in the sunbird schema, and MapSunBird() creates and migrates them at startup.
```csharp
public static ISunBirdBuilder UseSqlServer(this ISunBirdBuilder builder, string connectionString)
```
#### Parameters
`builder` ISunBirdBuilder
The builder returned by AddSunBird().
`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 builder, for chaining.
---
# Namespace HCI.SunBird.Data.SqlServer
### Classes
[SunBirdSqlServerExtensions](HCI.SunBird.Data.SqlServer.SunBirdSqlServerExtensions.md)
Stores SunBird content in SQL Server.
---
# 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
### UseSqlite\(ISunBirdBuilder, string\)
Stores SunBird content in the SQLite database at connectionString. SunBird's table names
start with SunBird_, and MapSunBird() creates and migrates them at startup.
```csharp
public static ISunBirdBuilder UseSqlite(this ISunBirdBuilder builder, string connectionString)
```
#### Parameters
`builder` ISunBirdBuilder
The builder returned by AddSunBird().
`connectionString` [string](https://learn.microsoft.com/dotnet/api/system.string)
The SQLite connection string, for example Data Source=App_Data/sunbird.db. The database file is created
if it does not exist, but its folder must exist.
#### Returns
ISunBirdBuilder
The same builder, for chaining.
---
# Namespace HCI.SunBird.Data.Sqlite
### Classes
[SunBirdSqliteExtensions](HCI.SunBird.Data.Sqlite.SunBirdSqliteExtensions.md)
Stores SunBird content in SQLite.
---
# Interface ISunBirdBuilder
Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md)
Assembly: HCI.SunBird.dll
Configures SunBird after . A data package adds the extension
method that chooses the database, such as UseSqlServer(connectionString) from HCI.SunBird.Data.SqlServer.
```csharp
public interface ISunBirdBuilder
```
## Properties
### 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)
---
# 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 (@inject ISunBirdPages Pages) or a PageModel. For a plain list of links, use
<sb-sub-pages />. Reads are cached in memory, so a public page view never writes to the database.
```csharp
public interface ISunBirdPages
```
## Methods
### 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> 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 , 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.
---
# Class SbFileTagHelper
Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md)
Assembly: HCI.SunBird.dll
An editable download link, such as a form or leaflet:
<sb-file key="referral-form" href="/docs/referral-form.pdf" show-size>Referral form</sb-file>.
Renders an <a> that keeps every attribute other than key, href and show-size.
Editors change its text and choose the PDF it gives from the media library. Without a saved value the link uses
its own href 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
### 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)?
### Key
The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any
sb-scope prefix.
```csharp
[HtmlAttributeName("key")]
public string? Key { get; set; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)?
### ShowSize
Adds the chosen document's type and size after the link text, such as "(PDF, 147 KB)", in a
<span class="sb-file-size">. 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
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# Class SbFragmentTagHelper
Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md)
Assembly: HCI.SunBird.dll
Shows a fragment: a partial view with , whose content is shared by every page
that shows it: <sb-fragment name="_Testimonials" />. 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
### Name
The partial view's name or path, as for <partial name="…" />.
```csharp
[HtmlAttributeName("name")]
public string? Name { get; set; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)?
## Methods
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# Class SbHtmlTagHelper
Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md)
Assembly: HCI.SunBird.dll
An editable rich-text slot: <sb-html key="intro"><p>Default</p></sb-html>. 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
### Key
The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any
sb-scope prefix.
```csharp
[HtmlAttributeName("key")]
public string? Key { get; set; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)?
## Methods
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# Class SbIconTagHelper
Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md)
Assembly: HCI.SunBird.dll
An editable icon from Google's Material Symbols (Outlined):
<sb-icon key="service-icon" name="home" class="feature-icon" />. Renders an inline
<svg> that keeps every attribute other than key and name. It is 1em square and drawn in
currentColor, so CSS sizes and colours it like text. It is hidden from screen readers unless the tag has an
aria-label, 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 name.
```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
### Key
The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any
sb-scope prefix.
```csharp
[HtmlAttributeName("key")]
public string? Key { get; set; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)?
### Name
The default icon's name in Material Symbols, such as home or call, 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
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# Class SbImageTagHelper
Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md)
Assembly: HCI.SunBird.dll
An editable image slot: <sb-image key="hero-image" src="/img/hero.jpg" alt="Our office" />. Renders an
img with the slot's published image and alt text, or else its own src and alt. Every other
attribute, such as class, width or loading, 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
### 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)?
### Key
The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any
sb-scope prefix.
```csharp
[HtmlAttributeName("key")]
public string? Key { get; set; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)?
### 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
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# Class SbLinkTagHelper
Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md)
Assembly: HCI.SunBird.dll
An editable link, such as a call-to-action button:
<sb-link key="cta" href="/contact" class="button">Get a quote</sb-link>. Renders an
<a> that keeps every attribute other than key and href. 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 href 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
### 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)?
### Key
The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any
sb-scope prefix.
```csharp
[HtmlAttributeName("key")]
public string? Key { get; set; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)?
## Methods
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# 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:
<sb-repeater key="gallery" count="3"><figure><sb-image key="picture" … /></figure></sb-repeater>.
The slots inside each entry are stored under the repeater's key and the entry's number, such as
gallery.2.picture. The tag itself is not rendered, so each entry's markup sits directly in its parent and
CSS such as :nth-child(even) 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
### 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)?
### Key
The repeater's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any
sb-scope 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)?
### 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
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# 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:
<sb-scope key="top"><partial name="_Hero" /></sb-scope> stores the partial's
hero-title slot as top.hero-title. 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
### 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
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# Class SbSeoTagHelper
Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md)
Assembly: HCI.SunBird.dll
Renders the page's <title>, meta description, canonical link, Open Graph tags and
twitter:card. Put <sb-seo /> in the
layout's <head>, in place of the <title>. It shows the values an editor saved in the page
settings, or else the defaults from and
. On a page without 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
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# 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: <sb-sub-pages /> renders
<ul><li><a href="/news/opening-day">Opening day</a></li></ul> and keeps any other
attributes, such as class, on the <ul>. It renders nothing when there are none. For other markup,
use .
```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
### 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)?
### 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
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# Class SbTableTagHelper
Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md)
Assembly: HCI.SunBird.dll
An editable table, such as a price list: <sb-table key="prices" class="price-list"> with a
<thead> and one or more <tbody> sections inside it. Renders a <table> that keeps
every attribute other than key. 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 <thead> as <th scope="col">,
each section a <tbody> whose optional heading row is one <th scope="rowgroup"> across every
column, and the first cell of each row a <th scope="row">.
```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
### Key
The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any
sb-scope prefix.
```csharp
[HtmlAttributeName("key")]
public string? Key { get; set; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)?
## Methods
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# Class SbTextTagHelper
Namespace: [HCI.SunBird.Web](HCI.SunBird.Web.md)
Assembly: HCI.SunBird.dll
An editable plain-text slot: <sb-text key="hero-title">Default text</sb-text>. 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
### Key
The slot's key, unique on its page: 1 to 100 ASCII letters, digits, '-', '_' or '.', including any
sb-scope prefix.
```csharp
[HtmlAttributeName("key")]
public string? Key { get; set; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)?
## Methods
### ProcessAsync\(TagHelperContext, TagHelperOutput\)
Asynchronously executes the with the given context and
output.
```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 that on completion updates the output.
#### Remarks
By default this calls into .
---
# 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
### MapSunBird\(IEndpointRouteBuilder\)
Applies any pending SunBird database migrations, then maps SunBird's endpoints, including the route that serves
pages editors create from templates ( ) at their addresses. Code pages always
win over those. Call it once at startup, after builder.Build().
```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 endpoints, for chaining.
#### Exceptions
[InvalidOperationException](https://learn.microsoft.com/dotnet/api/system.invalidoperationexception)
No database was configured on , no HCI.Email IEmailService or HCI.FileStorage
IFileStorageService is registered, SunBird:Email:From or SunBird:SiteUrl is not set,
Razor Pages is not added, or the and
attributes break a rule (a repeated template key, an unknown key in ChildTemplates, or sub-pages under a
page whose route has parameters).
---
# 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 @attribute [HCI.SunBird.Web.SunBirdFragment("testimonials")], and show the
partial with <sb-fragment name="_Testimonials" />. 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
### 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
### Key
The fragment's key.
```csharp
public string Key { get; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)
---
# 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
### AddSunBird\(IHostApplicationBuilder\)
Registers SunBird's services. Chain a data package's method on the result to choose the database, for example
builder.AddSunBird().UseSqlServer(connectionString).
```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 /sunbird/. The site needs
AddRazorPages() and MapRazorPages() for those pages. Create the first admin at /sunbird/setup
with the code set in SunBird:SetupCode. SunBird emails invites and password resets through the site's
HCI.Email IEmailService, so register one and set SunBird:Email:From and SunBird:SiteUrl.
Editors' images are kept through the site's HCI.FileStorage IFileStorageService, so register one of those too.
---
# 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:
[SunBirdPage("home")]. 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
### 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
### ChildTemplates
The keys of the templates ( ) editors may add pages from under this page, such
as ChildTemplates = ["article"]. When set, the editor bar shows New page 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)\[\]?
### Description
The page's default meta description, rendered by <sb-seo />. 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)?
### 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)
### Title
The page's default title for search engines and sharing, rendered by <sb-seo /> as the
<title>. 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)?
---
# 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 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
### 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)
### 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)
### Title
The page's published title.
```csharp
public string Title { get; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)
### Url
The page's address from the site root, such as /news/opening-day, including any path base.
```csharp
public string Url { get; }
```
#### Property Value
[string](https://learn.microsoft.com/dotnet/api/system.string)
---
# 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 : [SunBirdTemplate("article", Name = "Article")], and
list its key in 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
### 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
### 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)\[\]?
### 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)?
### 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)
### 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)?
---
# Namespace HCI.SunBird.Web
### Classes
[SbFileTagHelper](HCI.SunBird.Web.SbFileTagHelper.md)
An editable download link, such as a form or leaflet:
<sb-file key="referral-form" href="/docs/referral-form.pdf" show-size>Referral form</sb-file>.
Renders an <a> that keeps every attribute other than key, href and show-size.
Editors change its text and choose the PDF it gives from the media library. Without a saved value the link uses
its own href and content.
[SbFragmentTagHelper](HCI.SunBird.Web.SbFragmentTagHelper.md)
Shows a fragment: a partial view with , whose content is shared by every page
that shows it: <sb-fragment name="_Testimonials" />. 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: <sb-html key="intro"><p>Default</p></sb-html>. 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):
<sb-icon key="service-icon" name="home" class="feature-icon" />. Renders an inline
<svg> that keeps every attribute other than key and name. It is 1em square and drawn in
currentColor, so CSS sizes and colours it like text. It is hidden from screen readers unless the tag has an
aria-label, 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 name.
[SbImageTagHelper](HCI.SunBird.Web.SbImageTagHelper.md)
An editable image slot: <sb-image key="hero-image" src="/img/hero.jpg" alt="Our office" />. Renders an
img with the slot's published image and alt text, or else its own src and alt. Every other
attribute, such as class, width or loading, is kept.
[SbLinkTagHelper](HCI.SunBird.Web.SbLinkTagHelper.md)
An editable link, such as a call-to-action button:
<sb-link key="cta" href="/contact" class="button">Get a quote</sb-link>. Renders an
<a> that keeps every attribute other than key and href. 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 href and content.
[SbRepeaterTagHelper](HCI.SunBird.Web.SbRepeaterTagHelper.md)
Repeats its content once per entry, and lets editors add, remove and reorder the entries:
<sb-repeater key="gallery" count="3"><figure><sb-image key="picture" … /></figure></sb-repeater>.
The slots inside each entry are stored under the repeater's key and the entry's number, such as
gallery.2.picture. The tag itself is not rendered, so each entry's markup sits directly in its parent and
CSS such as :nth-child(even) 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:
<sb-scope key="top"><partial name="_Hero" /></sb-scope> stores the partial's
hero-title slot as top.hero-title. Scopes can nest. The tag itself is not rendered.
[SbSeoTagHelper](HCI.SunBird.Web.SbSeoTagHelper.md)
Renders the page's <title>, meta description, canonical link, Open Graph tags and
twitter:card. Put <sb-seo /> in the
layout's <head>, in place of the <title>. It shows the values an editor saved in the page
settings, or else the defaults from and
. On a page without it renders nothing.
[SbSubPagesTagHelper](HCI.SunBird.Web.SbSubPagesTagHelper.md)
Lists the published pages editors added under a page, newest first, as links: <sb-sub-pages /> renders
<ul><li><a href="/news/opening-day">Opening day</a></li></ul> and keeps any other
attributes, such as class, on the <ul>. It renders nothing when there are none. For other markup,
use .
[SbTableTagHelper](HCI.SunBird.Web.SbTableTagHelper.md)
An editable table, such as a price list: <sb-table key="prices" class="price-list"> with a
<thead> and one or more <tbody> sections inside it. Renders a <table> that keeps
every attribute other than key. 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 <thead> as <th scope="col">,
each section a <tbody> whose optional heading row is one <th scope="rowgroup"> across every
column, and the first cell of each row a <th scope="row">.
[SbTextTagHelper](HCI.SunBird.Web.SbTextTagHelper.md)
An editable plain-text slot: <sb-text key="hero-title">Default text</sb-text>. 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 @attribute [HCI.SunBird.Web.SunBirdFragment("testimonials")], and show the
partial with <sb-fragment name="_Testimonials" />. 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:
[SunBirdPage("home")]. 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 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 : [SunBirdTemplate("article", Name = "Article")], and
list its key in 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 . A data package adds the extension
method that chooses the database, such as UseSqlServer(connectionString) from HCI.SunBird.Data.SqlServer.
[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 (@inject ISunBirdPages Pages) or a PageModel. For a plain list of links, use
<sb-sub-pages />. Reads are cached in memory, so a public page view never writes to the database.
---
# 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, `` and ``, and a clearer edited-slot outline.
---
# 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).
- **``.** 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).
- **``.** 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.
---