Class ServiceExtensionCreator
Generates a service extension for a discovered AggregateCatalog aggregate: the service
class (fresh implementation, decorator, or infrastructure subclass), its DI wiring, an optional
initialization-pipeline task, layered asmdefs, a test stub and a README — in one pass, with rollback on
failure. Every generated aggregate mirrors the layout real Serenity aggregates use (see Unity/Score,
Unity/PlayerPrefsPersistence): a code layer (Application or Infrastructure) under
Services/, an Installation/ layer with Installers/ (and Settings/ for a
PipelineReplace task asset), an optional Tests/ layer, and a root README.md — each layer
owning its own folder and, unless Skip, its own asmdef. Mirrors the structure of
CutsceneCreator / ModalCreator: this class only mutates the project; a future wizard window
collects the ServiceExtensionCreator.Input.
Inheritance
ServiceExtensionCreator
Assembly: Serenity.ServiceExtensionCreator.Editor.dll
Syntax
public static class ServiceExtensionCreator
Methods
The generated code layer's folder/namespace segment: "Infrastructure" or "Application" for
the Service kind (see Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.ResolveServiceCodeLayerName(Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.Input)); always "Infrastructure" for every
other kind.
Declaration
public static string ComposeCodeLayerName(ServiceExtensionCreator.Input input)
Parameters
Returns
The generated code layer's kind-named subfolder for a Store/Repository/Gateway/Factory
extension point ("Stores"/"Repositories"/"Gateways"/"Factories") — used for a live preview. Null for
the Service kind (see ComposeCodeLayerName(Input) instead) and the Installer kind (no code
layer class is ever generated for it).
Declaration
public static string ComposeExtensionPointFolderSegment(ServiceExtensionCreator.Input input)
Parameters
Returns
The generated wiring subclass's type name for a Store/Repository/Gateway/Installer extension point —
used for a live preview during the Wiring/Output wizard steps, mirroring how Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.CreateBackendWiring(Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.BackendWiringContext,System.Collections.Generic.List{System.String},System.Collections.Generic.List{System.String},System.Collections.Generic.List{System.String})
and Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.CreateExtensionPoint(Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.Input) derive the same name for real. Empty for the Service kind (its
wiring name depends on the free-choice Wiring), the Factory kind (no wiring is
ever generated for it), a Repository/Gateway extension point with no discoverable installer hook, and
a bespoke Store contract with no backend hook (None).
Declaration
public static string ComposeExtensionPointWiringTypeName(ServiceExtensionCreator.Input input)
Parameters
Returns
The composed extension root every generated layer folder/namespace/asmdef is named from — the
composed type name minus its trailing kind suffix (e.g. "PostgresSQLPersistence" for a "PostgresSQL"
extension of the Persistence service, "PostgresBlob" for a "Postgres" extension of the BlobStore
extension point). Empty when ComposeServiceTypeName(Input) is.
Declaration
public static string ComposeRoot(ServiceExtensionCreator.Input input)
Parameters
Returns
Declaration
public static string ComposeRootNamespace(ServiceExtensionCreator.Input input)
Parameters
Returns
Computes the sanitized extension name (PascalCase, "Unity"-stripped for Infrastructure style) that
every generated file and type name is built from — used alongside ComposeServiceTypeName(Input)
for a live preview of the wiring/test/README file names while the wizard is still collecting input.
Declaration
public static string ComposeSanitizedExtensionName(ServiceExtensionCreator.Input input)
Parameters
Returns
Computes the generated service type name for the given input, without validating or creating
anything — used for a live preview while the user is still filling in the wizard.
Declaration
public static string ComposeServiceTypeName(ServiceExtensionCreator.Input input)
Parameters
Returns
ComposeTypeName(ExtensionPointDescriptor, Style, string)
Composes the generated type name for any extension point kind. A Service-kind InfrastructureSubclass
is named after its actual base ({Name}{BaseTypeName} off the — possibly rebased — descriptor's
ConcreteType): byte-identical to V1's
{Name}Unity{Aggregate}Service for the canonical base, base-aware for any other eligible base.
Every other Service style keeps {Name}{Aggregate}Service. Every other kind is simply
{Name}{ExtensionPointName} regardless of the chosen base — no "Unity" prefix (per the naming
rule: PostgresBlobStore, {Name}ScoreRepository, {Name}AudioPlayerGateway,
{Name}FilePersistenceDriversFactory, {Name}PersistenceInstaller — Name
already IS that per-kind suffix, having been derived from the contract type's name). Public and pure
so tests can pin the naming against a directly-constructed descriptor.
Declaration
public static string ComposeTypeName(ExtensionPointDescriptor descriptor, ServiceExtensionCreator.Style style, string sanitizedExtensionName)
Parameters
Returns
Validates the request, then generates every requested artifact in its layered folder. On any
failure every created path is rolled back so the project is left untouched (mirrors
CutsceneCreator / ModalCreator).
Declaration
public static ServiceExtensionCreator.Result Create(ServiceExtensionCreator.Input input)
Parameters
Returns
DescribeVirtualizationRequirement(ExtensionPointDescriptor)
The rejection every generation surface shows for a not-yet-virtualized user class — shared
by Validate(Input) and the wizard's step gating so the wording never drifts.
Declaration
public static string DescribeVirtualizationRequirement(ExtensionPointDescriptor descriptor)
Parameters
Returns
IsInfrastructureAvailable(AggregateDescriptor, out string)
True when the InfrastructureSubclass style is available for descriptor — at
least one eligible subclass base exists (any public, non-abstract, non-test implementer of
I{X}Service with overridable members, not just the canonical Unity{X}Service — see
EligibleSubclassBases). Shared by Validate(Input) and
ServiceExtensionCreatorWindow so the rule and its wording never drift apart.
Declaration
public static bool IsInfrastructureAvailable(AggregateDescriptor descriptor, out string reason)
Parameters
Returns
IsSubclassStyleAvailable(ExtensionPointDescriptor, out string)
True when the Subclass style is available for descriptor — at least one eligible
subclass base exists (see EligibleSubclassBases). Shared by
Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.ValidateExtensionPointStyle(Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.Input,Serenity.ServiceExtensionCreator.Editor.ExtensionPointDescriptor) and ServiceExtensionCreatorWindow so the rule and
its wording never drift apart (mirrors IsInfrastructureAvailable(AggregateDescriptor, out string) for the Service
kind). Never consulted for the Installer kind, which forces the Subclass style through its own
dedicated abstract-base path — see Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.ValidateExtensionPointStyle(Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.Input,Serenity.ServiceExtensionCreator.Editor.ExtensionPointDescriptor).
Declaration
public static bool IsSubclassStyleAvailable(ExtensionPointDescriptor descriptor, out string reason)
Parameters
Returns
The descriptor the Service-kind pipeline actually generates against: for the InfrastructureSubclass
style, the catalog descriptor rebased onto the resolved SubclassBaseTypeName
(canonical/first when empty); for every other style, the catalog descriptor untouched. An unresolvable
base name returns the descriptor untouched — Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.ValidateService(Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.Input) already rejected it, so
this fallback only ever feeds previews. Public so the wizard's asmdef-references preview reads the
same rebased descriptor real generation will.
Declaration
public static AggregateDescriptor ResolveEffectiveServiceDescriptor(ServiceExtensionCreator.Input input, AggregateDescriptor descriptor)
Parameters
Returns
ResolveUserClassDescriptor(IReadOnlyList<ExtensionPointDescriptor>, string, out string)
Resolves a Class-kind extension point by simple or full type name over userClassCatalog.
Unlike every other kind, two user classes may legitimately share a simple name across namespaces
(Enemies.Spawner / Items.Spawner) — a simple name matching several classes is
rejected with an actionable ambiguity error instead of silently first-picking, mirroring
Serenity.ServiceExtensionCreator.Editor.ServiceExtensionCreator.ResolveSubclassBase(System.Collections.Generic.IReadOnlyList{System.Type},System.String,System.String@)'s philosophy; a dotted name matches Type.FullName exactly.
Public and list-driven so tests can pin the resolution rules against constructed descriptors.
Declaration
public static ExtensionPointDescriptor ResolveUserClassDescriptor(IReadOnlyList<ExtensionPointDescriptor> userClassCatalog, string requestedName, out string ambiguityError)
Parameters
Returns
Returns a human-readable validation error, or null when the request is valid.
Declaration
public static string Validate(ServiceExtensionCreator.Input input)
Parameters
Returns
ValidateSubclassBaseSelection(IReadOnlyList<Type>, string)
Returns the resolver error a SubclassBaseTypeName selection would produce
against eligibleBases, or null when it resolves — the wizard's per-step twin of
the Validate(Input)-time check, shared so the rule and its wording never drift apart.
Declaration
public static string ValidateSubclassBaseSelection(IReadOnlyList<Type> eligibleBases, string requestedName)
Parameters
Returns