Skip to content

Add an Alias Names sample (OPC UA Part 17) - #849

Merged
romanett merged 4 commits into
masterfrom
romanett/ua-netstandard-issue-822-186094
Sep 2, 2026
Merged

romanett merged 4 commits into
masterfrom
romanett/ua-netstandard-issue-822-186094

Conversation

@romanett

@romanett romanett commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Proposed changes

A server/client pair which demonstrates OPC 10000-17, Alias Names: how a server publishes a searchable index of the tag names people actually use for its signals, and how a client turns one of those names into the NodeId it stands for.

The address space is laid out by structure the way engineering documentation describes a plant (Plant/Reactor/TemperatureMeasurement), and the tag list names the same signals the way the control system does (TIC101_PV). Neither naming scheme can be dropped: renaming the nodes would break every client which addresses the plant by structure, and a client which knows only TIC101_PV cannot browse for it, because the name is nowhere in the address space. ModelDesign.xml and PlantTags.cs are deliberately unrelated files — that separation is what the sample is about.

The issue was a title and a link to docs/AliasNames.md, so the shape below is a proposal; happy to adjust the scope.

Server

Publishes the index both ways Part 17 §9 allows, so the client can put them side by side:

Standard categories Application-defined categories
Where TagVariables (i=23479), fixed by the spec PlantTags + nested Reactor/Boiler, in the server's own namespace
Server does seeds a store, registers it with IAliasNameStoreRegistry hands a store to an AliasNameNodeManager
Client needs to know nothing — OpenStandardTagVariables knows the NodeId the namespace uri + identifier, or it browses for them
Browsable no yes, organized under the standard Aliases object
Methods FindAlias only FindAlias, FindAliasVerbose, AddAliasesToCategory, DeleteAliasesFromCategory, LastChange

Client

Browses the plant and names each node from the inventory via an AliasNameResolver (the reverse mapping), searches a category by wildcard, and reads the nodes it resolved — the "Resolves to" and "Value" columns are what prove the search answered with a usable address rather than a matching string.

The mutation buttons stay enabled for every account on purpose. The server grants SecurityAdmin to one account (secadmin), and seeing an anonymous session refused with BadUserAccessDenied is as much the point as seeing an administrator succeed. Searching needs no privilege — a tag list is not a secret.

Related Issues

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which adds functionality)
  • Test enhancement (non-breaking change to increase test coverage)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected, requires version increase of Nuget packages)
  • Documentation Update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc.
  • I have signed the CLA.
  • I ran tests locally with my changes, all passed.
  • I fixed all failing tests in the CI pipelines.
  • I fixed all introduced issues with CodeQL and LGTM.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added necessary documentation (if appropriate).
  • Any dependent changes have been merged and published in downstream modules.

Further comments

Two SDK limits documented rather than worked around

The doc the issue links to lives on UA-.NETStandard master, which is ahead of the 2.0.0-preview.2 this repository pins. preview.2 ships the full client and server AliasNames API (including PubSub), but not DiagnosticsNodeManager.MaterializeRegisteredAliasNameNodesAsync or AliasNameNodeManagerOptions.MaterializeAliasNodes — those appear around 2.0.262. Consequences:

  • The standard well-known categories can serve only FindAlias, since the published NodeSet instantiates no other Method node. The sample therefore declares AliasNameCapabilities.None on its standard descriptor and demonstrates the optional Methods on the application-defined categories, which do get them.
  • Browsable AliasNameType nodes (§6.2, what the CTT browses for) are unavailable.

I built against the pinned version rather than bumping the SDK, which felt well outside this issue. Both gaps are called out in the sample README, so they read as "not yet" rather than "not done". Happy to revisit if the repo is about to move to a newer preview.

One spec deviation found, recorded as a known issue

A change in a nested category advances that category's LastChange but not its ancestors', so a client watching only the PlantTags root does not see that a tag changed in Reactor. Part 17 §6.3.1 asks for the ancestor to move as well (and the SDK doc claims it does).

Rather than assert the broken behaviour, this uses the suite's existing KnownIssue mechanism: the expectation is written the right way round and reports itself as ignored until the stack fixes it, at which point the test fails and asks for the note to be removed. Worth filing upstream if maintainers agree with the reading.

Verification

  • Full solution builds clean — 0 errors, no new warnings.
  • Tier 0 configuration/catalog: 175 passed.
  • Tier 1 server + tier 2 client: passed. The tier 2 smoke test drives the real form — it connects, runs the full post-connect path (browse, resolver, search), asserts FindBTN was enabled, and disconnects cleanly.
  • Tier 1.5 AliasNamesNodeManagerTests: 10 passed, 1 ignored (the known issue above). Full tier 1.5 suite still 131 passed, so no regressions elsewhere.

Notes for reviewers

Three non-obvious things that shaped the code, all found by running the tests:

  • A category descriptor must carry the NodeId the category will really have, which needs the namespace index. The store is therefore seeded in the node manager factory's Create, where server.NamespaceUris exists — not in the server constructor. AliasNameNodeManager silently skips descriptors whose NodeId lies outside the namespace it owns.
  • InMemoryAliasNameStore searches a category together with its whole subtree. Each tag is seeded into its unit only; seeding the root as well returned every tag twice. A client which knows how the plant is divided narrows the search, one which does not asks the root.
  • AliasNameVerboseDataType carries no reference type — it has AliasNameCategoryId and ServerUris. A local target still yields one empty-string ServerUris slot rather than an empty array.

Not covered, and listed as such in the README

The well-known Topics category (§9.4), aliases pointing into another server via ServerUri, a custom IAliasNameStore over a database or MES, the ReferenceTypeFilter argument of FindAlias, and all of Annex D (the PubSub schema for distributing LastChange between servers).

🤖 Generated with Claude Code

A server/client pair which demonstrates OPC 10000-17: how a server publishes
a searchable index of the tag names people actually use for its signals, and
how a client turns one of those names into the NodeId it stands for.

The address space is laid out by structure the way engineering documentation
describes a plant, and the tag list names the same signals the way the control
system does. Neither naming scheme can be dropped, and a client which knows
only the tag name cannot browse for it, because the name is nowhere in the
address space. ModelDesign.xml and PlantTags.cs are deliberately unrelated
files: that separation is what the sample is about.

The server publishes the index both ways Part 17 clause 9 allows, so the
client can put them side by side:

* a store registered with IAliasNameStoreRegistry, which makes the standard
  well known TagVariables object (i=23479) answer FindAlias - a client needs
  no prior knowledge of the server at all;
* an AliasNameNodeManager serving a browsable, nested PlantTags/{Reactor,
  Boiler} category tree, which unlike the standard nodes can carry the
  optional Methods FindAliasVerbose, AddAliasesToCategory,
  DeleteAliasesFromCategory and LastChange.

The client browses the plant and names each node from the inventory using an
AliasNameResolver, searches a category by wildcard and reads the nodes it
resolved, and adds or deletes aliases at runtime. The mutation buttons stay
enabled for every account on purpose: the sample server grants SecurityAdmin
to one account, and seeing an anonymous session refused with
BadUserAccessDenied is as much the point as seeing an administrator succeed.

Two limits of the pinned SDK version are documented rather than worked around.
2.0.0-preview.2 does not ship the alias node materialization pass, so the
standard well known categories can only serve FindAlias and browsable
AliasNameType nodes (clause 6.2) are unavailable; the sample declares
AliasNameCapabilities.None on the standard descriptor and demonstrates the
optional Methods on the application defined categories instead.

Tier 1.5 tests cover the behaviour over a real session. One expectation is
recorded through the existing KnownIssue mechanism rather than asserted: a
change in a nested category advances that category's LastChange but not its
ancestors', contrary to clause 6.3.1, so a client watching only the root
misses it. The test is written the right way round and reports itself as
ignored until the stack fixes it.

Registered in the sample catalog, the server and client test factories, the
solution, the Run/Stop scripts, both READMEs and docs/TESTING.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@romanett

romanett commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

@copilot merge current master and bump sdk Version for the sample. Consder the changes made to the sdk since preview2

Copilot AI and others added 3 commits September 2, 2026 11:00
…dard-issue-822-186094

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
…packages

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
…ixed known issue

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>

Copilot AI commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@copilot merge current master and bump sdk Version for the sample. Consder the changes made to the sdk since preview2

Done in 5c793e9 (merge + bump) and 9c16373 (the SDK changes since preview.2).

Merge and bump. master merged cleanly. The sample now uses $(OpcUaNetStandardVersion) — 2.0.0-preview.4 — rather than pinning a version of its own, and both Program.cs files follow the DI config-XML bootstrap from #852 (SampleWinFormsHost.Run + AddSampleServer<T>("…Config.xml"), options.ConfigurationFile on the client). Two things the merge surfaced, both fixed here:

  • FileTransfer, StateMachines, RoleManagement and NodeManagement were left on 2.0.0-preview.2, which is not on nuget.org (NU1605), and still used the removed ConfigSectionName. They are migrated the same way, so the solution restores and builds again.
  • NodeManagement took port 62575, which this sample also used, and tier 0's ServerPortsDoNotCollide caught it. AliasNames moved to 62577.

What preview.2 could not do and preview.4 can. Both limits the PR description called out are gone, so the sample no longer documents them as gaps:

  • DiagnosticsNodeManager.MaterializeRegisteredAliasNameNodesAsync exists. The server now overrides CreateMainNodeManagerFactory to register the standard store before the address space is built, and its own ConfigurationNodeManager runs the materialization pass. The standard TagVariables category therefore carries FindAliasVerbose, AddAliasesToCategory, DeleteAliasesFromCategory and LastChange, plus one browsable AliasNameType node per tag — §6.2, what the CTT browses for. Its descriptor declares AliasNameCapabilities.All instead of None. New test TheStandardTagVariablesCategoryIsBrowsable covers it.
  • AliasNameNodeManagerOptions.MaterializeAliasNodes now defaults to true, so the application-defined categories get their alias nodes with no code change, and no duplicates appear.
  • The ancestor LastChange defect is fixed — a tag added in Reactor now advances PlantTags too. The KnownIssue wrapper is removed and the assertion stands on its own, and the README paragraph about it is gone.

Verified with tier 0 (183/183) and a headless harness driving the real sample server on Linux; the WinForms tiers need the Windows runner, and the workflow on this branch is waiting for approval.

@romanett
romanett merged commit 7fb52fd into master Sep 2, 2026
1 of 5 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.

Add Alias Names Sample (Part 17)

2 participants