# Description The CTT AliasName conformance units skip or fail against the ConsoleReferenceServer. `AliasName Base` skips with *"No instance of AliasNameType has been found in the address space"*; `Category Tags`, `Category Topics` and `Hierarchy` fail. Part 17 support landed in #3779, but aliases live only inside the `IAliasNameStore` — nothing is browsable, no `AliasNameCategoryType` is nested under another, and the Topics aliases point at the Server object rather than at a `PublishedDataSetType` instance. Part 17 §6.2 clients — the CTT among them — discover aliases by browsing, not only by calling `FindAlias`. This PR materializes the store contents as real nodes and fills the three gaps. ### SDK — `Opc.Ua.Server` * **`DiagnosticsNodeManager.MaterializeRegisteredAliasNameNodesAsync`** (new, opt-in) walks every registered `IAliasNameStore` and creates one `AliasNameType` instance per alias — BrowseName carrying the alias name, `AliasFor` references to its targets, and the inverse `HasAlias` reference on each local target (via `externalReferences`, since targets usually live in another node manager) — plus an `AliasNameCategoryType` instance for every store category the standard NodeSet does not already ship. Categories whose NodeId belongs to another node manager are skipped, so an `AliasNameNodeManager` that also registers its store with the server-wide registry does not get its NodeIds claimed twice. Opt-in: servers that only need the well-known `FindAlias` to answer from their store pay nothing. The nodes are a snapshot taken at address-space creation — later mutations change what `FindAlias` returns but do not add or remove nodes. * **Optional Part 17 methods on the well-known categories.** The same pass instantiates whatever a category declares through `AliasNameCapabilities`: `FindAliasVerbose` (§6.3.3), `AddAliasesToCategory` (§6.3.4), `DeleteAliasesFromCategory` (§6.3.5) and `LastChange` (§6.3.1). Mutations require a `SecurityAdmin` caller on a `SignAndEncrypt` channel; the gate previously private to `AliasNameNodeManager` moved to `AliasNameMethodDispatcher` so both paths share one implementation. * **`LastChange`** is now tracked per category rather than only for the standard `Aliases` node, and the duplicated seeding logic collapsed into one helper. ### Reference server * A nested **`Devices`** category under `TagVariables` holds `Pump1_Status` and `Heater_Power`. `FindAlias` on `TagVariables` still returns all six names through subtree recursion, while `FindAlias` on `Devices` returns exactly two — which is what the Hierarchy unit checks. * `ReferenceServerConfigurationNodeManager` creates three **`PublishedDataSet` instances** under `PublishedDataSets` (`i=17371`) and the Topics aliases now target them, satisfying the *"is a Dataset"* check. These are address-space instances only — no PubSub runtime is added, and the CTT only inspects the target's type definition. ## Note for reviewers — the reserved NodeIds The optional children are created at the NodeIds the OPC Foundation reserves for them (`Aliases.FindAliasVerbose` = `i=24054`, `TagVariables.LastChange` = `i=32854`, …). Those identifiers are allocated in `StandardTypes.csv` but are **not** declared in the ModelDesign and **not** present in the published NodeSet, because the standard address space does not instantiate optional children. The source generator therefore emits no parent-to-instance mapping and no `MethodIds` constants for them, so `DiagnosticsNodeManager.ReservedChildIds` supplies the nine method ids and the two missing `LastChange` ids explicitly, each traceable to its registry row. The 18 argument-property ids do have generated `VariableIds` constants and are referenced through those. I deliberately did **not** "fix" this by declaring the children in `tools/Opc.Ua.SourceGeneration.Core/Design/StandardTypes.xml`: that file is a verbatim copy of upstream `UA-ModelCompiler/Design.v105/StandardTypes.xml` (the only differences today are upstream being newer), so a local edit is lost on the next sync and the NodeIds would silently revert to factory-minted ones. `docs/AliasNames.md` records this. Worth raising upstream separately: `StandardTypes.csv` allocates `Aliases_FindAliasVerbose` and siblings while the ModelDesign never declares those children, so the ModelCompiler cannot emit ids it has itself reserved. If that is ever reconciled, `ReservedChildIds` becomes redundant and deletes cleanly. ## Related Issues - Fixes #4012 ## Checklist - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. — **not done: verified on net8.0 only** (Server 4710, InformationModel 1099, Core 4443, plus the client and features alias suites; AOT build clean). Needs a run against net framework and net10 before this leaves draft. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. ### Tests `tests/Opc.Ua.InformationModel.Tests/Aliasname*Tests.cs` mirror the four CTT conformance units and no longer `Assert.Ignore` where the CTT reported failures — the alias suite is 37 tests with no skips. New `AliasnameOptionalMethodsTests` covers the optional methods, their reserved NodeIds, the anonymous-caller denial, an add/find/delete round trip and `LastChange` advancing on mutation. One pre-existing test changed premise: `EnumerateSubCategoriesAsyncOnLeafReturnsEmptyAsync` asserted that `TagVariables` has no sub-categories, which this PR deliberately makes false. It now targets `Topics` (still a leaf) and a new test guards the nesting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: romanett <7413710+romanett@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com>
22 KiB
OPC UA Part 17 — Alias Names
OPC UA Part 17 defines a small but valuable address-space pattern: a hierarchy of human-readable alias names that point at one or more nodes via a non-hierarchical reference type. Clients can search the hierarchy by wildcard pattern and resolve a name to its targets without needing to know the target NodeId in advance — useful for tag-naming schemes (PI / SCADA / DCS), pub/sub topic registries, MES integration, and any scenario where humans pick names but machines need ids.
This stack ships full Part 17 support in Opc.Ua.Server (server
side) and Opc.Ua.Client (client side). The implementation covers:
| Spec section | Type / Method | Status |
|---|---|---|
| §6.2 | AliasNameType (browsable instance nodes) |
✔ opt-in — see below |
| §6.3.1 | AliasNameCategoryType (incl. nesting) |
✔ (nested instances opt-in — see below) |
| §6.3.1 | LastChange (VersionTime) |
✔ |
| §6.3.2 | FindAlias |
✔ |
| §6.3.3 | FindAliasVerbose |
✔ |
| §6.3.4 | AddAliasesToCategory |
✔ |
| §6.3.5 | DeleteAliasesFromCategory |
✔ |
| §7.2 | AliasNameDataType |
✔ |
| §7.3 | AliasNameVerboseDataType |
✔ |
| §8.2 | AliasFor reference type |
✔ |
| §9.2 | Well-known Aliases (i=23470) |
✔ wired; optional methods opt-in — see below |
| §9.3 | Well-known TagVariables (i=23479) |
✔ wired; optional methods opt-in — see below |
| §9.4 | Well-known Topics (i=23488) |
✔ wired; optional methods opt-in — see below |
| Annex D | PubSub replication (LastChange notifications) | ✔ transport-agnostic — see below |
Server side — Opc.Ua.Server.AliasNames
The server library exposes a pluggable backend (IAliasNameStore) plus
a default in-memory implementation. Apps assemble their alias inventory
inside a store, then either:
- Register the store directly with the server-wide
IAliasNameStoreRegistryso the standard well-knownAliases/TagVariables/Topicsnodes start dispatching through it, or - Wrap the store in an
AliasNameNodeManagerto expose application- defined categories under a custom namespace (with fullAddAliasesToCategory/DeleteAliasesFromCategorysupport).
Both approaches can be combined.
Quick start — serving standard categories
using Opc.Ua;
using Opc.Ua.Server;
using Opc.Ua.Server.AliasNames;
// inside CreateMasterNodeManager(IServerInternal server, ...):
var tagVariables = new AliasNameCategoryDescriptor(
ObjectIds.TagVariables,
QualifiedName.From(BrowseNames.TagVariables),
AliasNameCapabilities.FindAliasVerbose);
var store = new InMemoryAliasNameStore([tagVariables]);
store.Seed(ObjectIds.TagVariables, "TIC101_Setpoint",
new ExpandedNodeId("Scalar_Static_Double", refServerNs),
serverUri: null,
referenceTypeId: ReferenceTypeIds.AliasFor);
// ... seed more entries ...
((IAliasNameStoreRegistryProvider)server)
.AliasNameStoreRegistry.Register(store);
When a client calls Aliases.FindAlias (i=23476),
TagVariables.FindAlias (i=23485) or Topics.FindAlias (i=23494),
DiagnosticsNodeManager's late binder routes the call through the
registry to the matching store.
Quick start — application-defined categories
AliasNameNodeManager is a CustomNodeManager2 that owns a namespace
and creates its own category tree from the store's
RootCategories. Add it to your server's node-manager list:
var myRoot = new AliasNameCategoryDescriptor(
new NodeId("My/Category", myNamespaceIndex),
new QualifiedName("My/Category", myNamespaceIndex),
AliasNameCapabilities.All); // expose every optional method
var store = new InMemoryAliasNameStore([myRoot]);
nodeManagers.Add(new AliasNameNodeManager(server, configuration, store));
Options:
NamespaceUri— controls the namespace under which the manager registers its category instances. Defaults tohttp://opcfoundation.org/UA/AliasName/. Every category descriptor's NodeId must lie in this namespace; descriptors pointing anywhere else are skipped with a warning rather than claiming another manager's ids.MaterializeAliasNodes(defaulttrue) — creates one browsableAliasNameTypenode per alias, withAliasForreferences to its targets, exactly as the standard-node materialization below does. Disable to expose only the category tree.LinkToStandardAliasesObject(defaulttrue) — addsOrganizesexternal references from the well-knownAliases (i=23470)object to the manager's root categories so they show up in the standard browse tree.RequireSecurityAdminForMutations(defaulttrue) — rejectsAddAliasesToCategory/DeleteAliasesFromCategorycalls from unauthenticated users or sessions without theWellKnownRole_SecurityAdminrole on aSignAndEncryptchannel.RegisterWithServerRegistry(defaulttrue) — also registers the store withIAliasNameStoreRegistryso the well-known standard nodes see it.
Browsable alias nodes
Registering a store makes FindAlias answer from it, but the aliases
themselves stay inside the store — nothing in the address space shows
them. Part 17 §6.2 clients (the OPC Foundation CTT among them) also
browse for aliases, so a server under conformance test needs the
alias hierarchy materialized as real nodes.
Both materialization paths — AliasNameNodeManager for
application-defined namespaces and
DiagnosticsNodeManager.MaterializeRegisteredAliasNameNodesAsync for
the standard well-known nodes — run the same shared walker
(AliasNameNodeMaterializer), so they produce structurally identical
Part 17 trees and every fix lands in both at once.
DiagnosticsNodeManager.MaterializeRegisteredAliasNameNodesAsync does
that for every registered store: one AliasNameType instance per alias
— BrowseName carrying the alias name, AliasFor references to its
targets, and the inverse HasAlias reference added on each local
target — plus an AliasNameCategoryType instance for every store
category the standard NodeSet does not already ship (nested categories
included; a root category from the store is organized under the
standard Aliases object so it stays browse-discoverable). Remote
targets get their server URI registered in the server's ServerUris
table and referenced with the matching ServerIndex. Server-defined
BrowseNames that a descriptor left in namespace 0 — reserved for
OPC-Foundation-defined names — are re-homed into the diagnostics
namespace; Part 17 clients compare alias names ignoring the namespace,
so this is transparent to them. The pass is idempotent, and only
creates category nodes whose NodeId lies in the diagnostics namespace —
a descriptor pointing anywhere else is skipped with a warning rather
than claiming another manager's (or the standard NodeSet's) ids. Call
it from an overridden CreateAddressSpaceAsync, after base has
loaded the standard categories:
public override async ValueTask CreateAddressSpaceAsync(
IDictionary<NodeId, IList<IReference>> externalReferences,
CancellationToken cancellationToken = default)
{
await base.CreateAddressSpaceAsync(externalReferences, cancellationToken)
.ConfigureAwait(false);
await MaterializeRegisteredAliasNameNodesAsync(
externalReferences, cancellationToken).ConfigureAwait(false);
}
The same pass also instantiates the optional Part 17 methods —
FindAliasVerbose, AddAliasesToCategory, DeleteAliasesFromCategory
and LastChange — on every category whose descriptor declares them
through AliasNameCapabilities, including the well-known ones the
standard NodeSet ships without them:
var tagVariables = new AliasNameCategoryDescriptor(
ObjectIds.TagVariables,
QualifiedName.From(BrowseNames.TagVariables),
AliasNameCapabilities.All); // verbose + add + delete + LastChange
Mutation calls are gated on a SecurityAdmin caller over a
SignAndEncrypt channel and return BadUserAccessDenied otherwise.
It is opt-in: servers that only need FindAlias to answer from their
store pay nothing. The created nodes are a snapshot taken at
address-space creation — aliases added or removed later through
AddAliasesToCategory / DeleteAliasesFromCategory change what
FindAlias returns and advance LastChange, but do not add or remove
AliasNameType nodes.
The browse view carries AliasFor associations only: an entry a store
holds under an unrelated reference type is not a Part 17 §6.2 alias
association, so it is served by FindAlias (with a matching filter)
but not materialized. Entries stored under an AliasFor subtype are
materialized, but always as the base AliasFor reference — the
verbose record does not carry the concrete reference type.
Two things the caller controls:
- Give category descriptors a BrowseName in a namespace the server
owns. The store stamps that namespace onto every alias
QualifiedNameit reports, and the materializer re-homes ns=0 BrowseNames out of the reserved OPC Foundation namespace — with a server-owned descriptor namespace the query results and the browsable nodes agree, so a returned alias name resolves viaTranslateBrowsePathsToNodeIds. The reference server uses the diagnostics namespace for all its descriptors. - Part 17 §9 constrains
Topicsaliases toPublishedDataSetTypetargets. Neither the store norAddAliasesToCategoryenforces this — a server that exposes the mutation methods onTopicsshould only grant them to operators who preserve the constraint.
Quickstarts.ReferenceServer uses this in
ReferenceServerConfigurationNodeManager, which also creates the
PublishedDataSet instances and seeds the Topics aliases that point
at them — in the same place, so an alias can never target a dataset
that was not created. The nested Devices sub-category under
TagVariables is declared with the store seeding in
ReferenceServer.ConfigureAliasNameStore.
Custom backend
Implement IAliasNameStore to back the alias inventory with your own
storage (DB, file, MES, …). The interface is small:
public interface IAliasNameStore
{
IReadOnlyList<AliasNameCategoryDescriptor> RootCategories { get; }
event EventHandler<AliasStoreChangedEventArgs>? Changed;
uint? GetLastChange(NodeId categoryId);
bool OwnsCategory(NodeId categoryId);
ValueTask<IReadOnlyList<AliasNameDataType>> FindAliasAsync(...);
ValueTask<IReadOnlyList<AliasNameVerboseDataType>> FindAliasVerboseAsync(...);
ValueTask<StatusCode[]> AddAliasesAsync(...);
ValueTask<StatusCode[]> DeleteAliasesAsync(...);
}
The reference InMemoryAliasNameStore is thread-safe (SemaphoreSlim),
supports nested categories and emits Changed events that bubble up to
the address-space LastChange property. A mutation on a nested
category bumps — and notifies for — every ancestor category as well,
per Part 17 §6.3.1/§9.2: a category's LastChange reflects the most
recent change anywhere in its subtree, and the root Aliases value
reflects any change at all.
Client side — Opc.Ua.Client.AliasNames
The client library provides a high-level AliasNameClient plus a
caching AliasNameResolver.
using Opc.Ua.Client.AliasNames;
// Standard categories have hardcoded method NodeIds so the first call
// is one round-trip — no extra TranslateBrowsePaths probe needed.
AliasNameClient client = AliasNameClient.OpenStandardTagVariables(session);
IReadOnlyList<AliasNameDataType> result =
await client.FindAliasAsync("TIC%", referenceTypeFilter: null, ct);
AliasNameClient exposes the full Part 17 method surface:
FindAliasAsync(pattern, referenceTypeFilter, ct)FindAliasVerboseAsync(...)— throwsNotSupportedExceptionwhen the category does not expose the optional method.AddAliasesToCategoryAsync(IEnumerable<AliasNameAddRequest>, ct)DeleteAliasesFromCategoryAsync(IEnumerable<AliasNameDeleteRequest>, ct)EnumerateSubCategoriesAsync(ct)—IAsyncEnumerableof childAliasNameSubCategoryInfo.ReadLastChangeAsync(ct)— returns theVersionTime(ornullwhen the category does not exposeLastChange).
Per-call errors map to typed exceptions:
| Status code | Exception |
|---|---|
BadUserAccessDenied |
UnauthorizedAccessException |
BadNotSupported |
NotSupportedException |
BadNotImplemented |
NotSupportedException |
other BadXxx |
ServiceResultException |
AliasNameResolver — cached alias→NodeId
await using var resolver = new AliasNameResolver(
AliasNameClient.OpenStandardTagVariables(session));
IReadOnlyList<ExpandedNodeId> targets =
await resolver.ResolveAsync("TIC101_Setpoint", ct);
string aliasName = await resolver.ResolveAliasNameAsync(targets[0], ct);
Default refresh mode is Manual — callers invoke RefreshAsync
(or rely on lazy-load via ResolveAsync). Opt in to automatic cache
invalidation via one of:
AliasNameResolverRefreshMode |
Strategy | When to use |
|---|---|---|
Manual (default) |
ManualAliasNameRefreshStrategy |
Caller drives refresh explicitly. Safe everywhere. |
AutoOnLastChangePolling |
PollingAliasNameRefreshStrategy |
Server lacks Subscriptions or you want a fixed Read cadence. |
AutoOnLastChangeMonitoredItem |
MonitoredItemAliasNameRefreshStrategy |
Server supports Subscriptions. Push-based — no Read per interval. |
Custom strategies (e.g. the Annex D PubSub bridge in
Opc.Ua.Client.AliasNames.PubSub — see Annex D below) plug in via:
new AliasNameResolverOptions
{
RefreshStrategy = new MyCustomStrategy() // takes precedence over RefreshMode
}
The IAliasNameRefreshStrategy contract is tiny:
public interface IAliasNameRefreshStrategy : IAsyncDisposable
{
ValueTask StartAsync(AliasNameClient client, Action onInvalidate, CancellationToken ct);
}
Implementations watch for stale-cache triggers and invoke
onInvalidate whenever they detect a change. MonitoredItemAliasNameRefreshStrategyOptions
controls the underlying Subscription: it can be left owned (default
— created + deleted by the strategy) or set via SharedSubscription
to plug the monitored item into an externally managed subscription.
Disposing the resolver (await using / DisposeAsync) tears down the
strategy: timer for polling, MonitoredItem + Subscription for the
monitored-item variant. Disposal is idempotent and never throws.
Spec deviations / wrinkles
-
AliasNameDataType.ReferencedNodes— the wire format defines this asExpandedNodeId[](NodeSeti=18), notNodeId[]. The source-generatedAliasNameDataTypeis correct; the historical Quickstart sample usedNodeId[]and has been removed. -
Standard well-known nodes — the OPC UA NodeSet instantiates only
FindAliasonAliases/TagVariables/Topics(plusLastChangeonAliases), so the always-on binder wires just those. The optional methods are added byMaterializeRegisteredAliasNameNodesAsyncthrough the generatedAddFindAliasVerbose/AddAddAliasesToCategory/AddDeleteAliasesFromCategoryoptional- child helpers, for each category whose descriptor declares the matchingAliasNameCapabilities. Each child is created at the NodeId the OPC Foundation reserves for it (Aliases.FindAliasVerbose=i=24054,TagVariables.LastChange=i=32854, and so on).Those identifiers are allocated in the standard identifier registry (
StandardTypes.csv) but are deliberately not declared in the ModelDesign and not present in the published NodeSet — the standard address space does not instantiate optional children. A reserved id is a number set aside for the node if a server chooses to expose it, not a node the server must have. Consequently the source generator emits no parent-to-instance mapping and noMethodIdsconstant for them, andDiagnosticsNodeManager.ReservedChildIdssupplies the nine method ids and the two missingLastChangeids explicitly, each traceable to its registry row. The argument properties do have generatedVariableIdsconstants and are referenced through those.Do not "fix" this by declaring the children in
StandardTypes.xml: that file is a verbatim copy of the OPC Foundation's ModelDesign and is re-synced from upstream, so a local edit is lost on the next sync and the NodeIds silently revert to factory-minted ones. -
AliasNameCapabilities.AddAliasesToCategory/DeleteAliasesFromCategory— on anAliasNameNodeManagerthis defaults toSecurityAdmin-only viaAliasNameNodeManagerOptions.RequireSecurityAdminForMutations; on the standard well-known nodes the same check is always applied and cannot be opted out of. It requires both the role grant AND aSignAndEncryptchannel. -
ReferenceTypeFiltersemantics — null/empty andReferenceTypeIds.Referencesmatch every alias regardless of reference type. Otherwise matches are limited to aliases whose reference type is, or is a subtype of, the filter (usingServer.TypeTree.IsTypeOf).
Annex D — PubSub LastChange notifications
Part 17 Annex D defines a lightweight PubSub schema for alias-change
notifications between servers. The schema carries only each category's
current LastChange value (a VersionTime/uint) — subscribers learn
that a publisher's category changed, then refetch alias contents via
FindAlias/FindAliasVerbose if needed.
The data types (already emitted by the source generator):
| NodeId | Type | Fields |
|---|---|---|
i=24052 |
AliasCategoryUpdateDataType |
Category : PortableNodeId, LastChange : VersionTime |
i=24053 |
AliasUpdateDataType |
ApplicationUri : string, Categories : AliasCategoryUpdateDataType[] |
Server side — Opc.Ua.Server.AliasNames.PubSub
The server library exposes a transport-agnostic publisher that emits
fully-built AliasUpdateDataType messages whenever an
IAliasNameStoreRegistry-tracked store changes:
using Opc.Ua.Server.AliasNames;
using Opc.Ua.Server.AliasNames.PubSub;
// inside server startup (after the alias store is registered):
var resolver = new ServerPortableNodeIdResolver(server);
var publisher = new AliasNamePublisher(
registry: ((IAliasNameStoreRegistryProvider)server).AliasNameStoreRegistry,
portableResolver: resolver,
applicationUri: configuration.ApplicationUri);
publisher.AliasUpdateProduced += (_, e) =>
{
// Hand `e.Update` to your transport — e.g. publish a DataSetMessage
// through Opc.Ua.PubSub.UaPubSubApplication with the DataSet
// built by AliasUpdateDataSetFactory.Create(...).
};
Helpers shipped:
IPortableNodeIdResolver+ServerPortableNodeIdResolver— converts a localNodeIdinto the spec-requiredPortableNodeId(NamespaceUri + Identifier with namespace index stripped).AliasUpdateDataSetFactory.Create(dataSetClassId)— builds the fixed-by-specDataSetMetaDataTypedescribing theAliasUpdateDataSet (ApplicationUri : string,Categories : AliasCategoryUpdateDataType[]).AliasNamePublisher— subscribes to the registry, builds and emitsAliasUpdateDataTypemessages via theAliasUpdateProducedevent.
The library deliberately stays transport-agnostic: it raises the
fully-built AliasUpdateDataType and lets the application wire it
into Opc.Ua.PubSub.UaPubSubApplication (UDP / JSON / MQTT) or any
other transport that can carry the AliasUpdateDataType payload.
Client side — Opc.Ua.Client.AliasNames.PubSub
The client library mirrors the publisher:
using Opc.Ua.Client.AliasNames;
using Opc.Ua.Client.AliasNames.PubSub;
var reader = new AliasNamePubSubReader(
new AliasNamePubSubReaderOptions
{
ExpectedApplicationUri = "urn:opcfoundation:publisher",
});
// Hand incoming AliasUpdateDataType messages off to the reader from
// your transport (e.g. UaPubSubApplication.DataReceived, an MQTT
// subscriber callback, ...):
reader.Submit(receivedAliasUpdate);
// Wire reader into the resolver so the cache invalidates on every
// LastChange bump observed via PubSub.
await using var resolver = new AliasNameResolver(
AliasNameClient.OpenStandardAliases(session),
new AliasNameResolverOptions
{
RefreshStrategy = new AliasNamePubSubRefreshStrategy(reader),
});
Helpers shipped:
AliasNamePubSubReader— surfaces incomingAliasUpdateDataTypemessages as theAliasUpdateReceivedevent. OptionalExpectedApplicationUrifilter drops messages from other publishers.AliasNamePubSubRefreshStrategy : IAliasNameRefreshStrategy— bridges the reader into the resolver. Matches incoming entries by the resolver's categoryNamespaceUri+ identifier; firesInvalidateon any value-difference (wrap-safe — the comparison is inequality, not strict greater-than).
The PubSub bridge plugs into the same IAliasNameRefreshStrategy
extension point as the polling and monitored-item strategies — apps
can mix-and-match, e.g. fall back to polling on a particular category
while letting PubSub drive the rest.
See also
- OPC UA Part 17 specification: https://reference.opcfoundation.org/v105/Core/docs/Part17/
tools/Opc.Ua.SourceGeneration.Core/Design/StandardTypes.xml— Part 17 type definitions consumed by the source generator.tests/Opc.Ua.Server.Tests/AliasNames/— server-side unit tests.tests/Opc.Ua.Client.Tests/AliasNames/— mocked-session and live integration tests.samples/Quickstarts.Servers/ReferenceServer/ReferenceServer.cs—ConfigureAliasNameStoreshows how to seed and register a store.