Class SingletonSettingsCreationEngine<TSettings, TInput, TResult>
Template-method engine shared by every "create or update the ONE project-wide settings asset of this module" wizard: identity validation, target validation (folder shape and path collision), the transaction-backed creation with rollback, Addressables registration under the module's label, and the in-place update that restores the asset when a step after it fails. A concrete engine supplies only what genuinely differs between modules — its label, its default asset name, how it reads in a message, and which fields the input writes onto the asset.
Extracted from the ten near-identical *SettingsCreator classes (MenuServiceSettingsCreator
and ModalServiceSettingsCreator matched 85-94% line for line; five of the ten declared a
byte-identical ResolveNewAssetPath). ModalCreator is deliberately not one of them: it
authors many drafts, not one singleton, and belongs to
ResolveOrCreateByIdInFolder<TSettings>(string, string, string, SerenityAssetCreationTransaction, out bool)'s shape instead.
Adopt it the way BatchSoundEffectImporter adopts
BatchAudioAssetImportEngine{TItem,TInput,TResult,TAsset}: keep the module's
public static class as the caller-facing surface, hold one private sealed class Engine
instance behind it, and forward each public member in one line — the windows and tests that already
call the static creator never change.
The Id and the Guid are the engine's business, not a subclass's: it writes Id on both the create and the update path and seeds a fresh Guid exactly once, on creation. ApplyFields(TInput, TSettings) is therefore only ever handed the module's own fields to write, which is why the same override serves both paths.
Inherited Members
Namespace: Serenity.StructuredPersistence.Infrastructure.Editor.Authoring
Assembly: Serenity.UnityStructuredPersistence.Infrastructure.Editor.dll
Syntax
public abstract class SingletonSettingsCreationEngine<TSettings, TInput, TResult> where TSettings : ScriptableObject, IFoundationSettings where TInput : SingletonSettingsInput where TResult : SingletonSettingsResult, new()
Type Parameters
| Name | Description |
|---|---|
| TSettings | The module's concrete settings asset type. |
| TInput | The module's concrete request type. |
| TResult | The module's concrete outcome type. |
Constructors
SingletonSettingsCreationEngine()
Declaration
protected SingletonSettingsCreationEngine()
Properties
AssetTypeDisplayName
What the asset is called in a message an author reads, e.g. "Menu service settings" — it completes the output-folder rejection and the Undo label. The settings type's own singleton display name (Serenity.Global.Infrastructure.Editor.SerenitySingletonSettingsCatalog.AuthorFacingNameOf(System.Type)), so a module never declares a second name for the asset its ambiguity and folder sentences already name.
Declaration
protected virtual string AssetTypeDisplayName { get; }
Property Value
| Type | Description |
|---|---|
| string |
DefaultAssetName
File name (without extension) used when the request names none, or names one that sanitizes away entirely.
Declaration
protected abstract string DefaultAssetName { get; }
Property Value
| Type | Description |
|---|---|
| string |
FailureMessagePrefix
The subject of the rollback sentence, e.g. "Menu service settings" for "Menu service settings generation failed and was rolled back: …". The same name as AssetTypeDisplayName: every singleton display name already reads at the start of a sentence.
Declaration
protected virtual string FailureMessagePrefix { get; }
Property Value
| Type | Description |
|---|---|
| string |
ModuleLabel
The Addressables module label stamped on everything this engine creates.
Declaration
protected abstract string ModuleLabel { get; }
Property Value
| Type | Description |
|---|---|
| string |
Methods
ApplyFields(TInput, TSettings)
Writes the module's own fields from the request onto the asset. Not the Id and not the Guid — the engine owns both. Called directly by the create path; the update path calls ApplyFieldsToExisting(TInput, TSettings) instead, which forwards here by default.
Declaration
protected abstract void ApplyFields(TInput input, TSettings settings)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input | |
| TSettings | settings |
ApplyFieldsToExisting(TInput, TSettings)
Writes the request onto an asset that already exists. Defaults to ApplyFields(TInput, TSettings) — override only when an update must treat a field differently from a create, e.g. a collection field that can be authored outside the wizard and so must be merged into the existing asset's value on update rather than overwritten the way a fresh asset's field is simply assigned.
Declaration
protected virtual void ApplyFieldsToExisting(TInput input, TSettings settings)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input | |
| TSettings | settings |
Create(TInput)
Validates the request, then either updates the asset named by ExistingSettingsAssetPath or creates a brand-new one. Either way a failure leaves the project as it found it: a created asset is deleted, and an existing one edited before the failure is put back.
Declaration
public TResult Create(TInput input)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input |
Returns
| Type | Description |
|---|---|
| TResult |
CreateExtraAssets(TInput, TSettings, SerenityAssetCreationTransaction, TResult)
Assets a module creates alongside its settings singleton, inside the same transaction so
one failure rolls the whole run back. Track every created path with
Serenity.Global.Infrastructure.Editor.SerenityAssetCreationTransaction.TrackCreatedAsset(System.String), and every pre-existing
asset edited on the way with
SerenityAssetCreationTransaction.TrackExistingAsset.
Declaration
protected virtual void CreateExtraAssets(TInput input, TSettings settings, SerenityAssetCreationTransaction transaction, TResult result)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input | |
| TSettings | settings | |
| SerenityAssetCreationTransaction | transaction | |
| TResult | result |
CreateFromDraft(TSettings, string, string, bool)
Draft-based counterpart of Create(TInput): commits every field draft carries
onto the resolved-or-created asset via ResolveAndCommit<TSettings>(TSettings, string, string, string, SerenityAssetCreationTransaction),
instead of the narrower ApplyFields(TInput, TSettings) field list — so a wizard built on
Serenity.Global.Infrastructure.Editor.SerenityWizardDraftLifecycle`1 (a SerenityWizardScratchDraft.FromExisting
seed) can never silently revert a field it does not draw to its declared default, the same
guarantee SettingsAssetDraftCommit provides for every one of its callers.
The engine still owns identity through that shared call:
draft's own Guid is overwritten regardless of what it already carried, and its Id
travels with everything else UnityEditor.EditorUtility.CopySerialized(UnityEngine.Object,UnityEngine.Object) copies — trimmed first here,
the same rule every other SettingsAssetDraftCommit caller applies to its own
Id-shaped fields before committing. Skips ApplyFields(TInput, TSettings)/ApplyFieldsToExisting(TInput, TSettings)
entirely — a draft-based caller owns the whole asset, not a per-module field subset.
Declaration
public TResult CreateFromDraft(TSettings draft, string explicitSettingsAssetPath, string newSettingsFolderPath, bool registerAsAddressables)
Parameters
| Type | Name | Description |
|---|---|---|
| TSettings | draft | Every field to write: an already-seeded scratch draft from an existing asset for an update-in-place run, or a freshly populated one for a create. |
| string | explicitSettingsAssetPath | Explicit target path — empty resolves the project-wide singleton. |
| string | newSettingsFolderPath | Folder a brand-new asset is created under when none exists. |
| bool | registerAsAddressables | Whether to register a newly created asset as Addressable. |
Returns
| Type | Description |
|---|---|
| TResult |
CreateNewWithin(TInput, SerenityAssetCreationTransaction)
The part of a creation run that happens inside a transaction: ensures the output folder, creates the asset with its Id, a fresh Guid and the module's own fields, and verifies it landed on disk — without committing or rolling back anything itself. Public so a module's larger creator can fold the singleton's creation into ITS transaction alongside its other assets, rather than have this engine commit a separate one first. The created asset is at ResolveNewAssetPath(TInput), which is also the transaction's Serenity.Global.Infrastructure.Editor.SerenityAssetCreationTransaction.LastCreatedPath until the caller tracks something else.
Declaration
public TSettings CreateNewWithin(TInput input, SerenityAssetCreationTransaction transaction)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input | |
| SerenityAssetCreationTransaction | transaction |
Returns
| Type | Description |
|---|---|
| TSettings |
ResolveNewAssetPath(TInput)
The path a brand-new asset would be created at. Pure — computes the path without resolving, creating or ensuring anything, so a window's read-only preview and CreateNewWithin(TInput, SerenityAssetCreationTransaction) both read one formula.
Declaration
public string ResolveNewAssetPath(TInput input)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input |
Returns
| Type | Description |
|---|---|
| string |
UpdateExistingInPlace(TInput)
Updates the already-validated asset named by ExistingSettingsAssetPath in place. Public so a module's larger creator can update the singleton it resolved itself, without paying for a second Validate(TInput) pass over a request it already validated.
Declaration
public TResult UpdateExistingInPlace(TInput input)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input |
Returns
| Type | Description |
|---|---|
| TResult |
Validate(TInput)
Full validation, re-checked at creation time so Create(TInput) is safe to call independently of a window's live validation.
Declaration
public SerenityCreationValidationResult Validate(TInput input)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input |
Returns
| Type | Description |
|---|---|
| SerenityCreationValidationResult |
ValidateBeyondIdentity(TInput, SerenityCreationValidationResult)
Validation beyond identity that must NOT leak into ValidateIdentity(TInput)'s live, folder-less check: a value rule that reads other fields (a bounds check, a duplicate-name scan) or a non-blocking warning about an optional reference. Runs only inside Validate(TInput), once identity itself passed — skipped when it did not, so a full validation never reports a module error on top of an identity error.
Declaration
protected virtual void ValidateBeyondIdentity(TInput input, SerenityCreationValidationResult result)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input | |
| SerenityCreationValidationResult | result |
ValidateExtra(TInput, SerenityCreationValidationResult)
Validation beyond identity: the module's own fields (a controller id, a backend choice). Runs inside ValidateIdentity(TInput) — and therefore inside Validate(TInput) too, since Validate(TInput) begins by calling ValidateIdentity(TInput) — so a window's live identity check sees it as well as a full validation. Contrast ValidateBeyondIdentity(TInput, SerenityCreationValidationResult), which runs only on a full Validate(TInput).
Declaration
protected virtual void ValidateExtra(TInput input, SerenityCreationValidationResult result)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input | |
| SerenityCreationValidationResult | result |
ValidateIdentity(TInput)
Id-only validation, usable before a folder choice has been made.
Declaration
public SerenityCreationValidationResult ValidateIdentity(TInput input)
Parameters
| Type | Name | Description |
|---|---|---|
| TInput | input |
Returns
| Type | Description |
|---|---|
| SerenityCreationValidationResult |