You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The hosting AddNodeManager(namespaceUri, Action<INodeManagerBuilder> build) overload always creates a FolderState named ReferenceServer under the Objects folder before the build callback runs. The name is a hard-coded constant in the internal FluentNodeManager, it is not documented, it cannot be configured, and every consumer of the hosting fluent API ends up with an extra top-level object called ReferenceServer in their address space regardless of what their server is.
publicoverrideasyncValueTaskCreateAddressSpaceAsync(...){
...FolderState root =CreateRootFolder(namespaceIndex);// line 103awaitAddPredefinedNodeAsync(root,cancellationToken).ConfigureAwait(false);NodeManagerBuilderbuilder=CreateFluentBuilder(namespaceIndex);m_build(builder);
...}privatestaticFolderStateCreateRootFolder(ushortnamespaceIndex){conststringbrowseName="ReferenceServer";// line 118varroot=newFolderState(null){NodeId=newNodeId(browseName,namespaceIndex),BrowseName=newQualifiedName(browseName,namespaceIndex),DisplayName=newLocalizedText(browseName),TypeDefinitionId=ObjectTypeIds.FolderType};root.AddReference(ReferenceTypeIds.Organizes,true,ObjectIds.ObjectsFolder);returnroot;}
Introduced in #3957. The only in-tree consumer that wants this exact folder is AddReferenceServer (OpcUaServerBuilderExtensions.cs ~line 1268), which registers AddNodeManager("http://opcfoundation.org/UA/ReferenceServer", nm => nm.Node("ReferenceServer")), i.e. the demo preset leaked its root-folder name into the general-purpose API. docs/NodeManagers.md describes the hosting AddNodeManager overload without mentioning that a root folder is created at all.
Why it matters
A server built with services.AddOpcUa().AddServer().AddNodeManager("urn:mycompany:plant", b => ...) exposes an object called ReferenceServer under Objects. There is no way to rename, retype or suppress it without dropping to a hand-written FluentNodeManagerBase subclass, which defeats the point of the one-shot overload.
FluentNodeManager and FluentNodeManagerFactory are internal sealed, so the folder cannot be replaced by subclassing either.
The NodeId of the folder is the string "ReferenceServer" in the caller's namespace, so two managers registered through this overload in different namespaces both carry a node with that identifier.
Stop creating an implicit root folder by default. The build callback already has everything it needs to create and place its own root (builder.CreateInstance<FolderState>(...).Configure(n => n.UnderObjectsFolder()), or AddObject on an existing node).
If a convenience root is still wanted, make it opt-in and named by the caller, for example an overload AddNodeManager(namespaceUri, rootBrowseName, build) or an options object (RootFolder = new QualifiedName("Plant", ns)), with the folder handed to the callback as the builder's root node rather than looked up by a magic browse name.
Move the ReferenceServer folder creation into AddReferenceServer, which is the only place that name belongs.
Document the behaviour of the overload in docs/NodeManagers.md either way.
Affected tests: tests/Opc.Ua.Server.Tests/Hosting/ServerFluentApiHostingTests.cs (line ~587 does builder.Node("ReferenceServer") against the implicit folder) and OpcUaServerBuilderExtensionsCoverageTests.cs (AddReferenceServer*).
Summary
The hosting
AddNodeManager(namespaceUri, Action<INodeManagerBuilder> build)overload always creates aFolderStatenamedReferenceServerunder the Objects folder before the build callback runs. The name is a hard-coded constant in the internalFluentNodeManager, it is not documented, it cannot be configured, and every consumer of the hosting fluent API ends up with an extra top-level object calledReferenceServerin their address space regardless of what their server is.Where
src/Opc.Ua.Server/Hosting/FluentNodeManagerFactory.cs:Introduced in #3957. The only in-tree consumer that wants this exact folder is
AddReferenceServer(OpcUaServerBuilderExtensions.cs~line 1268), which registersAddNodeManager("http://opcfoundation.org/UA/ReferenceServer", nm => nm.Node("ReferenceServer")), i.e. the demo preset leaked its root-folder name into the general-purpose API.docs/NodeManagers.mddescribes the hostingAddNodeManageroverload without mentioning that a root folder is created at all.Why it matters
services.AddOpcUa().AddServer().AddNodeManager("urn:mycompany:plant", b => ...)exposes an object calledReferenceServerunder Objects. There is no way to rename, retype or suppress it without dropping to a hand-writtenFluentNodeManagerBasesubclass, which defeats the point of the one-shot overload.FluentNodeManagerandFluentNodeManagerFactoryareinternal sealed, so the folder cannot be replaced by subclassing either.NodeIdof the folder is the string"ReferenceServer"in the caller's namespace, so two managers registered through this overload in different namespaces both carry a node with that identifier.UnderObjectsFolder()/OrganizedBy(...)+CompleteConfigureAsync), so the implicit root folder is no longer needed as the anchor for build-created nodes.Proposal
builder.CreateInstance<FolderState>(...).Configure(n => n.UnderObjectsFolder()), orAddObjecton an existing node).AddNodeManager(namespaceUri, rootBrowseName, build)or an options object (RootFolder = new QualifiedName("Plant", ns)), with the folder handed to the callback as the builder's root node rather than looked up by a magic browse name.ReferenceServerfolder creation intoAddReferenceServer, which is the only place that name belongs.docs/NodeManagers.mdeither way.Affected tests:
tests/Opc.Ua.Server.Tests/Hosting/ServerFluentApiHostingTests.cs(line ~587 doesbuilder.Node("ReferenceServer")against the implicit folder) andOpcUaServerBuilderExtensionsCoverageTests.cs(AddReferenceServer*).