Skip to content

Finish the fluent node manager migration (#876) - #880

Merged
romanett merged 5 commits into
masterfrom
romanett/finish-fluent-node-manager-migration
Sep 10, 2026
Merged

romanett merged 5 commits into
masterfrom
romanett/finish-fluent-node-manager-migration

Conversation

@romanett

@romanett romanett commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Closes #876. The hooks the migration was waiting for landed with
OPCFoundation/UA-.NETStandard#4418 and #4442, so all five node managers that issue lists move
onto them. Master is merged in, and every NuGet package in the repository is updated - the OPC
UA preview included - in two commits of their own.

Moved off AsyncCustomNodeManager

Workshop/DataAccess, Workshop/PerfTest and Workshop/HistoricalAccess. All three serve an
address space which is computed rather than stored, over standard OPC UA types, so none of them
gets a ModelDesign - there would be no type model to declare in one. They derive from
FluentNodeManagerBase and drive the builder themselves (CreateFluentBuilder → Configure →
RegisterAuthoredNodesAsync → CompleteConfigureAsync → SealConfigurationAsync), the way
StateMachines and AlarmCondition already do, and their GetManagerHandleAsync /
ValidateNodeAsync pairs become virtual node families registered with ResolveNodes.

PerfTest needs two families: only the register variables carry a read handler, and a family
which wires one may only ever resolve variables. That read handler is new, and it is what the
move requires. The server now holds on to the node of a monitored variable, but the register
never writes into that node - it queues the changed value straight into the monitored item,
bypassing the sampling machinery - so a read of a subscribed variable would otherwise answer
with the value the variable was synthesized with.

HistoricalAccess keeps its folders and its startup items as predefined nodes; what becomes a
family is the tail which is not registered up front, a file which turns up in the archive root
and is turned into an item on the first request naming it. The family carries the two monitored
item hooks as well, which the archive items need and the live variables fall through.

Overrides whose stated reason is gone

Samples/Opc.Ua.Sample/MemoryBuffer loses GetManagerHandleAsync and its four monitored item
overrides. The tags are a virtual node family; the create hook keeps the three refusals the
sample promises (filter, index range, data encoding) and hands the stack a factory for the item
the buffer publishes into, which the stack then registers, modifies and deletes like any other
monitored item. GenerateFactory = false goes with them: the partial can read the
ApplicationConfiguration now, so the hand-written factory has no reason left to exist.

Workshop/Aggregation loses its handle resolver and validator to a virtual node family - which
also wires the browser that walks the remote address space - and its CreateMonitoredItemsAsync
and OnDeleteMonitoredItemsCompleteAsync overrides to the builder-level batch hooks.

What deliberately stays a service override

Aggregation's batched Read, Write and Call forwarding and its modify and monitoring mode hooks:
they turn one service call into one downstream service call, which the per-node fluent handlers
would undo - the opposite of what an aggregating server is for. The class remark used to say
they were waiting for #4397/#4398/#4399; it now says what they are actually there for.

HistoricalAccess's ReadAsync: it refreshes a stale archive from disk before any attribute of
an item is read, and holds the archive lock across the read so the simulation cannot change the
value fields halfway through it. A per-node read handler would only see the Value attribute.

Package updates

Two commits. The first takes everything nuget.org reports as outdated:
Microsoft.CodeAnalysis.NetAnalyzers to 10.0.401, the Microsoft.Extensions.* and
Microsoft.EntityFrameworkCore.* packages to 10.0.12, Microsoft.NET.Test.Sdk to 18.10.0 and
NUnit3TestAdapter to 6.3.0 - the last two had drifted apart between the test projects and are
now the same version everywhere. After it, every package in the repository other than the OPC UA
stack sits on its latest stable release.

The second moves the stack itself, pinned in one place in targets.props:
2.0.301.4181-preview → 2.0.312.3680-preview (SDK master 0e600c0f), eleven master builds on.
Everything it broke, it broke by having grown the thing the samples were filling in for, so each
fix is a deletion:

  • SampleServerBuilderExtensions is gone. Its own summary said it held "the registrations the
    samples make on the server builder of the stack which the stack itself has no method for" - the
    stack now has all of them, so every call resolved to two candidates. AddNodeManagers grew a
    better shape on the way: the loaded ApplicationConfiguration is handed to the callback instead
    of being resolved from the container, which is what ConfiguredNodeManagerFactories and the loop
    over it in SampleServerFactory existed for. Both are gone too.
  • NodeBrowser grew the async browse seam the Aggregation Browser was waiting for, in so many
    words ("There is no async browser seam in the stack to migrate to yet"). NextAsync is an
    override now, so the asynchronous browse and translate paths await the call to the aggregated
    server instead of blocking a request worker on it.
  • The model generator emits a protected constructor taking the namespace URIs, which is what
    TestData had a hand-written constructor and GenerateFactory = false for. The sample names its
    second namespace in AdditionalNamespaceUris now, takes the generated factory, and creates the
    test system in an override of the three-argument LoadPredefinedNodesAsync.

Verified against the packages of the nuget-publish run which produced that version (run
34435263403, its opcua-packages-34435263403 artifact used as a local folder feed, because
GitHub Packages cannot be queried without a token). A reviewer with feed access should confirm
2.0.312.3680-preview is the version they want
- it is simply the newest master build at the
time of writing.

Verification

Whole solution rebuilds with the same set of warning codes as before either bump, 0 errors.
All five test tiers, on the new SDK:

  • SampleNodeManagers.Tests - 192 passed, 1 pre-existing skip
  • SampleServers.Tests - 126 passed
  • SampleClientModels.Tests - 445 passed, 3 skipped
  • SampleClients.Tests - 65 passed
  • SampleConfiguration.Tests - 247 passed

🤖 Generated with Claude Code

romanett and others added 3 commits September 9, 2026 07:19
The hooks the migration was waiting for landed with UA-.NETStandard#4418 and
#4442. Four of the five node managers of samples #876 move onto them; the fifth,
HistoricalAccess, is deliberately left out of this change.

Workshop/DataAccess and Workshop/PerfTest move off AsyncCustomNodeManager. Both
serve an address space which is computed rather than stored, so neither gets a
ModelDesign - there is no type model to declare. They derive from
FluentNodeManagerBase and drive the builder themselves, the way StateMachines
and AlarmCondition already do, and their GetManagerHandleAsync/ValidateNodeAsync
pairs become virtual node families registered with ResolveNodes.

PerfTest needs two families: only the register variables carry a read handler,
and a family which wires one may only ever resolve variables. That read handler
is new and is what the move requires: the server now holds on to the node of a
monitored variable, and the register never writes into that node - it queues
into the monitored item directly - so a read would otherwise answer with the
value the variable was synthesized with.

Samples/Opc.Ua.Sample/MemoryBuffer loses GetManagerHandleAsync and its four
monitored item overrides. The tags are a virtual node family; the create hook
keeps the three refusals the sample promises and hands the stack a factory for
the item the buffer publishes into, which the stack then registers, modifies and
deletes like any other. GenerateFactory = false goes with them: the partial can
read the ApplicationConfiguration now, so the hand-written factory has no reason
left to exist.

Workshop/Aggregation loses its handle resolver and validator to a virtual node
family, and its CreateMonitoredItemsAsync and OnDeleteMonitoredItemsCompleteAsync
overrides to the builder-level batch hooks. The batched Read, Write and Call
forwarding and the modify and monitoring mode hooks stay service overrides -
they turn one service call into one downstream call, which the per-node fluent
handlers would undo. The class remark said they were waiting for those hooks;
it now says what they are actually there for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last of the five node managers of #876. Same shape as DataAccess and
PerfTest: no ModelDesign, because there is no type model to declare for a file
archive, so the manager derives from FluentNodeManagerBase and drives the
builder itself.

The folders and the items which exist at startup stay predefined nodes. What
becomes a virtual node family is the tail which is not registered up front: a
file which turns up in the archive root is turned into an item on the first
request which names it. The family carries the two monitored item hooks as
well, which is what the archive items need to know that somebody is watching
them; the live variables fall through them as they did before.

ReadAsync stays a service override, and now says why: it refreshes a stale
archive from disk before any attribute of an item is read, and holds the archive
lock across the read. A per-node read handler would only see the Value
attribute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@romanett romanett changed the title Finish the fluent node manager migration (#876), except HistoricalAccess Finish the fluent node manager migration (#876) Sep 10, 2026
romanett and others added 2 commits September 10, 2026 13:04
Everything nuget.org reports as outdated:

  Microsoft.CodeAnalysis.NetAnalyzers                   10.0.400 -> 10.0.401
  Microsoft.EntityFrameworkCore.Design                  10.0.11  -> 10.0.12
  Microsoft.EntityFrameworkCore.SqlServer               10.0.11  -> 10.0.12
  Microsoft.Extensions.DependencyInjection.Abstractions 10.0.11  -> 10.0.12
  Microsoft.Extensions.Hosting                          10.0.11  -> 10.0.12
  Microsoft.Extensions.Logging.Abstractions             10.0.11  -> 10.0.12
  Microsoft.Extensions.Logging.Console                  10.0.11  -> 10.0.12
  Microsoft.NET.Test.Sdk                       18.8.1 / 18.9.0   -> 18.10.0
  NUnit3TestAdapter                            6.2.0  / 6.3.0    -> 6.3.0

The two test packages had drifted apart between the test projects; both are
now the same version everywhere.

The OPC UA stack itself is left alone. It is pinned in one place in
targets.props to a preview from the OPC Foundation GitHub Packages feed, which
needs a token to query, so what the latest preview is cannot be established
here - and a bump of it is its own change.

The analyzer bump reports no new rule: a rebuild of the whole solution before
and after it produces the same set of warning codes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last package left after the previous commit: the OPC UA stack itself,
pinned in one place in targets.props. 2.0.301.4181 -> 2.0.312.3680-preview
(SDK master 0e600c0f), eleven master builds on.

Everything it broke, it broke by having grown the thing the samples were
filling in for, so each fix is a deletion:

* `SampleServerBuilderExtensions` is gone. Its own summary said it held "the
  registrations the samples make on the server builder of the stack which the
  stack itself has no method for" - the stack now has all of them, so every
  call resolved to two candidates. `AddNodeManagers` grew a better shape on the
  way: the loaded ApplicationConfiguration is handed to the callback instead of
  being resolved from the container, which is what `ConfiguredNodeManagerFactories`
  and the loop over it in `SampleServerFactory` existed for. Both are gone too,
  and the hosted server adds those node managers itself.

* `NodeBrowser` grew the async browse seam the Aggregation `Browser` was
  waiting for, in so many words: "There is no async browser seam in the stack
  to migrate to yet. NextAsync is the real implementation and is what the
  sample would call once the stack offers one." It is an override now, and the
  asynchronous server browse and translate paths await it instead of blocking a
  request worker on the call to the aggregated server. `Next` stays for the
  synchronous consumers and says so.

* The model generator emits a protected constructor taking the namespace URIs,
  which is what TestData had a hand-written constructor and
  `GenerateFactory = false` for. Its two-argument sibling chains to it with
  `null`, which was ambiguous against the sample constructor. The sample names
  its second namespace in `AdditionalNamespaceUris` now, takes the generated
  factory, and creates the test system in an override of the three-argument
  `LoadPredefinedNodesAsync` - one step earlier than Configure, which is where
  the typed node states need it.

Verified on the packages of the nuget-publish run which produced this version
(run 34435263403, artifact opcua-packages-34435263403 used as a local folder
feed, because GitHub Packages cannot be queried without a token): the whole
solution rebuilds with the same set of warning codes as before the bump, and
all five test tiers pass unchanged - 192/126/445/65/247.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@romanett
romanett merged commit 3975fce into master Sep 10, 2026
4 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Finish the fluent node manager migration: the hooks that blocked it have landed

1 participant