Register the sample node managers through DI and finish the node manager migrations preview.4 unlocks - #856
Merged
Conversation
…e server constructors Every server sample gets a composition root next to its entry point (<Sample>ServerHosting.cs with Add<Sample>Server) which registers the server as the hosted server of the stack and its node managers with the server builder of the stack (AddNodeManager<TFactory>). The server classes register nothing themselves any more: the constructors lose their AddNodeManager calls, Aggregation and FileTransfer lose the CreateMasterNodeManagerAsync / OnServerStarting overrides which registered configuration-dependent factories (the aggregation composition root adds one factory per configured endpoint through the server factory of the container and starts the reverse connect manager as a hosted service; FileTransfer gets a factory which reads the configuration on first use), the GDS servers register the ManagedApplications and onboarding registrar node managers as factories and the sample GDS server creates the managers of every registered factory next to the GDS one, the UA sample server registers its three node managers with AddSampleNodeManager for the application-instance host of the legacy sample server and client, and the reference server registers the quickstart factories as node manager registrations. AddSampleServer gains an Action<IOpcUaServerBuilder> overload; SampleApplicationHostedService applies the node manager registrations of the container to the server it starts, the way the hosted server of the stack does. The test harness (SampleServerHost) now hosts each sample through the same composition root its Program.Main uses, in a generic host with the certificate stores redirected into the temporary PKI, the opc.tcp base addresses restored to localhost (the configuration loader of the stack rewrites them to the machine name) and the same bind retry as before. SampleServerFactories hands out the registration instead of a server factory. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace the hand-built boilers in Configure - CreateInstanceOfBoilerType, two manual Organizes references and AddPredefinedNodeSynchronously - with the manager-scoped builder.CreateInstance(...).Configure(OrganizedBy(plant)) the preview.4 SDK offers. The instance is materialised from the type model with the symbolic names set before the New override mints the parsed node ids, root before children, so the ids are the same as before. Move the view membership onto the IsNodeInView(context, viewId, node) overload: the ContinuationPoint overload delegates to it and the public IsNodeInViewAsync reaches it directly on preview.4, so both paths share the discipline filter. IsReferenceInView is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The node manager still carried the preview.2 workarounds: it overrode LoadPredefinedNodesAsync only to capture the external references, reported the instance namespace after construction through SetNamespaces plus RegisterNamespaceManager, and built Boiler #2 by hand with the generated type factory, AddPredefinedNodeSynchronously and AddExternalReference. The 2.0.0-preview.4 SDK covers all of that: AdditionalNamespaceUris on the [NodeManager] attribute puts the instance namespace into the generated constructor and factory, the manager-scoped CreateInstance materializes Boiler #2 from the type model with node ids minted through New for the whole subtree, and UnderObjectsFolder writes the inverse Organizes reference the generated partial publishes to the Objects folder through CompleteConfigureAsync. Boiler #1 is resolved through the builder as the typed state the model loader created. The New override and the simulation stay as they were. The namespace uri is a string literal because the generator evaluates the attribute before the Namespaces constants it emits exist, and the untyped Configure partial is kept because the Simulation extension does not accept the generated typed builder proxy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…w.4 fluent surface Boiler: [NodeManager(AdditionalNamespaceUris)] reports the instance namespace at construction, replacing the SetNamespaces + RegisterNamespaceManager hack. Boiler #2 is created with the manager-scoped builder.CreateInstance(...).Configure(OrganizedBy) which runs Create (so OnAfterCreate still wires the simulation), mints ids through New and registers the instance; the forward Organizes edge on the Boilers folder is added by CompleteConfigureAsync. UpdateDisplayName and StartSimulation stay. MemoryBuffer: flipped from a hand-written AsyncCustomNodeManager to a [NodeManager] partial with both namespaces on the attribute; the hand constructor and the LoadPredefinedNodesAsync override are gone, the buffer creation moved into Configure (imperative Create + AddChild + AddPredefinedNodeSynchronously so the ids stay buffer names in the Instance namespace). GetManagerHandleAsync and the four monitored item overrides stay in the user partial, with the class remarks pointing at OPCFoundation/UA-.NETStandard#4397 and #4399. The factory stays hand-written (GenerateFactory = false) because preview.4 keeps no ApplicationConfiguration for Configure; it parses the buffer configuration and chains to the generated ctor. Deleted the unreferenced NodeSet2/PredefinedNodes/Types.bsd/Types.xsd/NodeIds.csv. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…deManagerBase Both Workshop servers have no ModelDesign by design, so they now drive the fluent builder by hand the way the generated node manager of a ModelDesign does: predefined nodes, CreateFluentBuilder(...).Configure(...), CompleteConfigureAsync(externalReferences), Seal(). StateMachines: the Machine object, the two state machines and the cause methods keep their test-pinned numeric ids and stay hand-built. The link below the Objects folder and the HasNotifier link to the Server object are inverse references written in Configure and published by CompleteConfigureAsync, which also registers the machine as a root notifier. The interlock is a builder property (Writable + typed OnWrite), the transition counter a builder property with a bound IValueUpdater. The explicit AddLastTransition on the Operation machine is gone: preview.4 materializes LastTransition with the transitions. BindExecutable and BindCause stay. AlarmCondition: the areas and the sources are registered as predefined nodes, so the on-demand GetManagerHandleAsync/ValidateNodeAsync overrides and the ParsedNodeId parsing they existed for are gone; New keeps minting the same ids for the alarms and dialogs. Each root area declares an inverse HasNotifier to the Server object in Configure and CompleteConfigureAsync publishes it and registers the root notifier. The one second system/audit event timer is a builder Simulation tick, so Dispose only owns the underlying system and the suppression engine. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ject The generated partial of a [NodeManager] class now calls CompleteConfigureAsync after Configure, so the inverse Organizes and HasNotifier references the root folder carries are published to the Objects folder and the Server object by the SDK; the stashed external reference dictionary and the two AddExternalReference calls go, and the root becomes a root notifier through the same pass. The Status object is created through the parent-scoped builder with the generated instance factory and its properties are bound to value updaters, which replaces three copies of the value-and-ClearChangeMasks block with one helper. Cleanups from the migration report: the local-node branches in ReadAsync and WriteAsync were dead (handles of predefined nodes carry their node, so the base class never asks for them to be validated), the SessionClosing event subscription is replaced by the SessionClosingAsync override the master node manager fans out, and the duplicated summary on GetOrCreateSubscription is gone. The class remarks now say why the service overrides stay on a proxy over a lazily materialised address space and which SDK issues track the missing hooks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Instances model of the Workshop DataTypes server was the last checked-in ModelCompiler output of the sample. It is now compiled by the OPC UA source generator from Instances/ModelDesign2.xml/.csv, and DataTypesNodeManager is a [NodeManager] partial: the generator emits the factory and the loading of the instance model, the user partial adds the type model of the shared library in front of it and registers the encodeables of both models through the generated extensions. The type model's design is handed to the server project as a second pair of AdditionalFiles so the children inherited from DriverType keep their access levels and default values (the [ModelDependency] payload carries neither). Removed: the generated Classes/Constants/DataTypes .cs files, the .uanodes, .bsd, .xsd, NodeSet and PredefinedNodes artefacts and BuildDesign.bat of the Instances model. Open on 2.0.0-preview.4: the generated node sets keep structure default values as the design's XML without a TypeId, and XmlDecoder.ReadExtensionObjectBody never resolves a body by its element name, so PrimaryVehicle/VehiclesInLot load as raw XML with a null type id. Two DataTypesNodeManagerTests fail because of it (DriverOfTheMonthCarriesAStructuredValue, GeneratedActivatorsDecodeTheSamplesStructures). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tests The source generator of 2.0.0-preview.4 serves a ModelDesign structure default value as an ExtensionObject with a null TypeId (OPCFoundation/UA-.NETStandard#4401). The two DataTypes expectations which depend on the value are recorded through KnownIssue.RecordAsync so they report ignored and fail the moment the stack ships the fix; TESTING.md and the sample README name the issue. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
romanett
added a commit
that referenced
this pull request
Sep 4, 2026
…tateMachines fix Master registered the sample node managers through per-sample composition roots (#856) and fixed the StateMachines tier 2 failure at its root - the test pressed the wrong cause (#857). The known-issue park this branch carried for that failure is superseded by the fix and removed, together with its TESTING.md note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed changes
Two things, on the 2.0.0-preview.4 packages master already builds against.
Every sample server registers its node managers through the container, not in its server class. Each server project gets a composition root next to its entry point (
<Sample>ServerHosting.cs,services.Add<Sample>Server(...)) which registers the server as the hosted server of the stack and its node managers with the stack's server builder (server.AddNodeManager<TFactory>()).Program.Mainand the tests share that one registration. The server constructors lose theirAddNodeManagercalls; theCreateMasterNodeManagerAsyncoverride of the Aggregation server and theOnServerStartingoverride of the FileTransfer server, which registered configuration-dependent factories, are gone:ApplicationConfigurationin its constructor. FileTransfer gets aFileTransferNodeManagerFactorywhich reads the configuration on first use; Aggregation needs one factory per configured endpoint, so its composition root adds them from the container's server factory (which runs with the configuration loaded) and starts the reverse connect manager as a hosted service after the server;ManagedApplicationsand onboarding-registrar node managers as factories whose stores come from the container, andSampleGlobalDiscoveryServercreates the managers of every registered factory next to the GDS one (the GDS base server of the stack ignores registered factories);AddSampleNodeManager<TFactory>()- the same registration the stack makes - whichSampleApplicationHostedServicenow applies before it starts the server; the reference server registers the quickstart factories the same way.AddSampleServergains anAction<IOpcUaServerBuilder>overload. The test harness (SampleServerHost) hosts every sample in a generic host through its composition root, with the certificate stores redirected into the temporary PKI and the opc.tcp base addresses restored tolocalhost(the configuration loader of the stack rewrites them to the machine name).SampleServerFactorieshands out the registration instead of a server factory.The node manager migrations preview.4 unlocks (see the audit that preceded this: the pinned package now carries Objects-folder placement through
CompleteConfigureAsync/UnderObjectsFolder, the root-levelCreateInstance,AdditionalNamespaceUrisand the cross-model generator fixes):LoadPredefinedNodesAsynccapture, theSetNamespaces+RegisterNamespaceManagerhack and the hand-built second boiler are replaced by the attribute andbuilder.CreateInstance(...).Configure(n => n.UnderObjectsFolder() / OrganizedBy(...)); only theNewoverride remains. Views moves its view membership onto theviewIdoverload the stack now reaches.[NodeManager]partial with its buffers built inConfigure; the handle and monitored-item overrides stay (no fluent surface for them, Fluent node manager: no surface for on-demand (virtual) nodes; managers must still override GetManagerHandleAsync/ValidateNodeAsync UA-.NETStandard#4397 / #4399), the stale NodeSet/bsd/xsd artefacts are deleted.ModelDesign2.xmlacross the project boundary, the ModelCompiler outputs and.uanodesare deleted, the README describes the new setup.FluentNodeManagerBasesubclasses driving the builder; AlarmCondition loses its on-demand handle resolution.Documentation:
Samples/Hosting/README.mdanddocs/TESTING.mddescribe the composition roots and the hosted harness.Related Issues
Types of changes
Checklist
Further comments
Local results on this branch: tier 0 183/183, tier 1 115/115, tier 1.5 160 passed / 3 skipped (the two recorded DataTypes expectations against UA-.NETStandard#4401 and the environment-gated UserAuthentication test), tier 2 60/61 before master was merged in; the one tier-2 failure,
ClientReceivesItsNotifications(StateMachines), was a test defect fixed on master by #857, and the test passes on this branch since that merge.Still on
AsyncCustomNodeManageron purpose: DataAccess, PerfTest and HistoricalAccess wait on the SDK extensions filed above (on-demand nodes, monitored-item lifecycle hooks, historian defaults); Methods, UserAuthentication and Empty need a ModelDesign first.🤖 Generated with Claude Code