Options and events in Program.cs
ServerOptions holds what only code can say: file converters, folder paths, how settings are loaded,
and the lifecycle callbacks.
builder.AddRelatudeDB(options => {
options.FileConverters.Add(new SkiaImageConverter(1)); // optional: tried before the built-in converters
options.DefaultDataFolderPath = "data"; // relative to the content root, or absolute
// applied at every start, locked on the settings pages, never saved to a file
options.OnServerSettingsInit = s => s.TokenCookieName = "MyToken";
options.OnDatamodelInit = (dm, database) => dm.AddNamespace<IVenue>();
options.OnStoreInit = db => db.RegisterTransactionPlugin(new AuditPlugin());
options.OnStoreOpenBackground = db => Seeder.SeedIfEmpty(db);
});| Option | What it does |
|---|---|
FileConverters |
Your own file converters, tried before the built-in image and video converters. |
DefaultDataFolderPath |
The root data folder. Default: the content root. |
DefaultTempFolderPath |
The scratch folder, emptied at every start. Default: relatude.data.temp. |
SettingsLoader |
An ISettingsLoader that replaces relatude.db.json. |
ConfigurationSectionName |
The configuration section merged over the settings. Default RelatudeDB; null turns it off. |
UseSettingsOverridesFile |
false makes the settings pages write relatude.db.json instead of the overrides file. |
SettingsOverridesFilePath |
Keeps relatude.db.overrides.json at this path instead of with the default database. |
AddCustomSettings(…) |
Registers a set of custom settings (above). |
CreateUrlManager |
Makes each database's URL manager (§18). |
The callbacks run in this order:
| Callback | When | Use it for |
|---|---|---|
OnServerSettingsInit |
after the settings are read and merged | server settings set by code, the database list |
OnContainerSettingsInit |
per database | storage providers, file stores, model sources |
OnStoreSettingsInit |
per database | the engine settings (LocalSettings) |
OnDatamodelInit |
after the model sources have loaded | adding types from code |
OnStoreInit |
store created, not yet open | transaction plugins, task runners |
OnStoreOpen |
store open; the open waits for it | light work only |
OnStoreOpenBackground |
store open, on a background thread | seeding, warm-up |
OnStoreClose |
host shutdown | cleanup |
Two things to know before you rely on them:
- The server catches every exception in a callback. It writes the exception to the startup log and carries on. If a callback seems to do nothing, look there.
- Seed in
OnStoreOpenBackground.OnStoreOpenholds up the open, and while a store opens every request gets a 503 progress page.
AddRelatudeDB can be called any number of times. Every call configures the same
ServerOptions, so a library and the host can each call it, in any order. A second UseRelatudeDB on
the same application does nothing. There is one server per process, so a second application throws.
A library hooks into the start with services, not callbacks. The callbacks belong to the host: a
host that assigns OnStoreInit after a library has chained onto it drops the library's part. A
library registers these services instead (namespace Relatude.DB.NodeServer):
| Service | Called | Use it for |
|---|---|---|
IDatamodelInitializer.Initialize(datamodel, database) |
for every datamodel, right after OnDatamodelInit |
the library's model types |
IStoreInitializer.Initialize(store) |
for every store before it opens, right after OnStoreInit |
transaction plugins, task runners, jobs |
IStoreOpenHandler.OpenedAsync(store) |
for every store once open, in the background, beside OnStoreOpenBackground |
seeding, work that needs an open store |
public sealed class AuditInitializer : IStoreInitializer {
public void Initialize(NodeStore store) => store.RegisterTransactionPlugin(new AuditPlugin());
}
services.TryAddEnumerable(ServiceDescriptor.Singleton<IStoreInitializer, AuditInitializer>());Open handlers run one after another, in the order they were registered. If one throws, the exception is logged and the next one still runs. The admin UI also builds datamodels to check a draft model, which runs the datamodel initializers too, so an initializer should only add types.