Platforms
The framework is two halves. The core — everything under NE.Standard.UI.* except the Web packages —
knows nothing about HTML, HTTP, SignalR or a browser: it compiles views, runs controllers, keeps the two in sync
through a stream of updates, and hands a platform a description of what to draw. A platform renders that
description, carries the updates to a client and the client's changes back, and implements the handful of
services a controller reaches through its context. Web is the platform that ships today; this page is the
contract a second one builds against.
What the core gives a platform
- A compiled view.
IUIHost.ResolveViewAsyncanswers aUIViewResolution: the route, the navigation request, the session, and aCompiledView— component nodes with their properties, the binding templates and indexes, the events, interactions, validation rules, item templates and dialogs, all addressed by id. A platform walks the nodes and draws each by itsTypeKey; ids are stable for the life of the process, so what it drew can be addressed by every later update. - A runtime per client.
IUIHost.AttachRuntimeAsynccreates or finds theIUIRuntimebehind a route with a controller, keyed by session, address and window (UIRuntimeKey,UIPersistenceOptions.Lifetime). The runtime answers the initial values (BuildInitialChangeSetAsync,BuildInitialCollectionChangesAsync), takes the client's writes (ProcessChangeSetAsync), runs commands (ProcessEventAsync), reads item windows (RequestItemWindowAsync) and flushes what the controller changed in the meantime (FlushAsync, driven by the core's own scheduler). - A stream of updates. Everything the client has to change arrives as a
ServerChangeSetofServerUIUpdates — a value, a collection change, a validation message, a full resync — each naming a component address and a property. A platform applies them to whatever it drew. - Client effects. A command or an interaction can raise a
ClientEffect(focus, scroll, navigate, open a dialog, show a notification, copy to the clipboard, ...).ClientEffectKindslists the built-in names; a platform runs the ones it implements and ignores the rest with a log line — a kind is a request, not a guarantee.DownloadFileEffectis the one shaped by the web — it names a path the browser fetches — and a platform whose download service saves the file itself never raises it. - The words the chrome needs.
UIStringsnames every word the framework draws that no author property sets — a picker's "Today", a notification's "Close" — with English text; the platform asks theITranslatorfor the session's language and draws that. - The theme.
UIThemeis twoUIColorPalettes, aUITypography, aUIShapeand a focus-ring switch — values, not CSS. The web turns them into custom properties; another platform turns them into brushes. - Client-side rules. Filter and sort rules on a plain or virtualized items host run on the client.
UIComparisonEvaluatorin the core is the reference for what an operator answers — text comparison unless the operator is numeric,nullreading as the empty string, a non-number comparing false under every numeric operator — and a platform's client follows it, or the two halves disagree about which row is visible.
What a platform implements
UIStartupBase refuses to start without these four; the web registers its own in WebStartupBase:
| Service | What it does |
|---|---|
IUIUpdateSink |
Pushes a ServerChangeSet to every attached instance of a runtime, and a command's result to the one instance that raised it. |
IUIDialogService |
Opens and closes a dialog declared by the view, for one connection. |
IUIDownloadService |
Stages a stream or a byte array for the client to fetch as a file — StandardDownloadService, in Core, does the staging over IUIFileStore; the platform supplies only IUIDownloadAddressProvider.AddressOf(token), turning a staged token into the address the client fetches it from (the web: /_ne/files/{token}). An in-process platform can write the content straight into a save dialog instead and needs no address at all. |
IUIUploadService |
Answers the files a FileInput selection carries, by selection id and file id, scoped to the session — StandardUploadService, also in Core, over IUIFileStore. |
Optional, and worth having:
IUserSessionResolver— the shipped resolver loads the session the client presented and issues a new id when it presented none; a platform only replaces it to source identity differently. What the platform does own is carrying the id: the web puts it in anHttpOnlycookie named byUISessionOptions.ClientKey; an in-process platform holds it in memory.IUIContentAddressResolver— where the platform serves content anIUIContentProvideranswers by key (an avatar, a chat background);UIContentAddressis the default implementation, a fixed prefix the platform's startup constructs once at registration (new UIContentAddress(WebContentEndpoint.Prefix)on the web) and registers as the singleton. Reachable asUIApplication.Contentand, for a controller,Context.Content.docs/FILES.md§8.- A theme-persistence helper —
SetThemeEffectboth puts the client into a theme and reports the choice back to the session, so the next render agrees; the session write isUserSessionStoreExtensions.SetThemeModeAsync, a Core helper overIUserSessionStorea platform's theme-switch handler calls (the web:WebUIHub.SetThemeAsync).
And the glue that has no interface, because it is the platform's own shape:
- A connection is a
UIInstance— a transport id, aWindowIdand the navigation request — wrapped with the session into aUIHandle. A window is one top-level surface showing one address (a browser tab, a desktop window); one handle per attached client, and the runtime keeps every attached instance and the update sink sends to all of them. - Resolve, then attach. A page load calls
ResolveViewAsync(which runs the view filters and the route authorization) and thenAttachRuntimeAsync; the runtime'sIsInitializedsays whether the controller'sOnInitializeAsynchas run.DetachRuntimewhen the connection goes; the core keeps the runtime forDisconnectedRetentionso a reconnect finds it. - The phase.
ResolveViewAsynctakes aUIViewRequestPhase:Openfor the request that opens the view and can hand the client a new session id,Attachfor a live connection attaching to it. The web calls both, once each per page load; a platform with one step passesOpen— that is the phase in which a pending session-id rotation afterSignInAsyncis carried out, so a platform that never passes it never rotates. - Two-way values. A platform decides which of its controls write back and sends a
ClientChangeSetofClientValueUIUpdates; the core accepts a value only for a property declared writable (UIBindingCapabilities.TargetToSource) and coerces it to the property's type (RecursiveValueCoercion). - Items. A static list is rendered by the platform from the compiled template; a bound list arrives as
collection inserts and is rendered from the same template on the client. Every item is keyed by its
Id, and an address inside a template carries the keys of the enclosing rows as dynamic parameters — the walk that resolves a template against an item isItemContextin the core (NE.Standard.UI.Compiled), which the web's renderers call and its client mirrors inbinding-template-evaluator.ts, held to one shared corpus of cases so the two cannot drift. - A dialog's own dismissal is a client-raised event.
UIDialog.OnCloseregisters a command on the dialog's content component under the reserved event nameclose; a platform raises it only when the viewer dismisses the dialog (Escape, the backdrop) and never when the server closes it itself, or a controller cannot tell the two apart. - A bound value written as a URL must be checked before it reaches the client. An
href, an imagesrcor a backgroundurl()built from a bound value is ajavascript:/non-image-data:injection surface on both the render path and the live-patch path; the web checks both through one shared helper (WebUrlSafety) rather than trusting the render path and forgetting the patch path or vice versa. A second platform needs the equivalent check on whatever it treats as "safe to write as a link or an image source." - Chrome a component's picker enforces is advisory only.
FileInput.MaxFileSize(the web writesdata-ui-file-max-size) and any similar picker-side limit are the client refusing early for a better experience; the real limit is still enforced wherever the bytes are received, since a client can simply not send the attribute a picker chrome reads. UITheme.PressRippleis a look a theme may switch off, not a mechanism a platform must implement. The web reads it at render (data-ui-press-rippleon the root) and answers it with a client engine; a platform that draws presses another way is free to ignore the flag.
Where the seam is drawn
Contracts/andCore/reference nothing above them and no web package;Web.AbstractionsreferencesShell, neverCore, so a render add-on does not drag the engine in.- Nothing in the core is
internalthat a platform needs: the web platform reaches the host throughIUIHostand the application throughUIApplication, both public.UIHostitself is internal on purpose. - The wire is JSON only where a wire exists. The converters on
UIComponentId,UIProperty,ServerUIUpdateandClientEffectlive with the types so every channel agrees; an in-process platform never serializes and never touches them.