Skip to content

Hosting AddNodeManager(namespaceUri, build) always creates a root folder named "ReferenceServer" #4388

Description

@romanett

Summary

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.

Where

src/Opc.Ua.Server/Hosting/FluentNodeManagerFactory.cs:

public override async ValueTask CreateAddressSpaceAsync(...)
{
    ...
    FolderState root = CreateRootFolder(namespaceIndex);          // line 103
    await AddPredefinedNodeAsync(root, cancellationToken).ConfigureAwait(false);
    NodeManagerBuilder builder = CreateFluentBuilder(namespaceIndex);
    m_build(builder);
    ...
}

private static FolderState CreateRootFolder(ushort namespaceIndex)
{
    const string browseName = "ReferenceServer";                  // line 118
    var root = new FolderState(null)
    {
        NodeId = new NodeId(browseName, namespaceIndex),
        BrowseName = new QualifiedName(browseName, namespaceIndex),
        DisplayName = new LocalizedText(browseName),
        TypeDefinitionId = ObjectTypeIds.FolderType
    };
    root.AddReference(ReferenceTypeIds.Organizes, true, ObjectIds.ObjectsFolder);
    return root;
}

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.
  • Since Fluent: publish Configure-created nodes' references on foreign-manager nodes (Objects folder placement) #4331 the builder can place nodes under Objects itself (UnderObjectsFolder() / OrganizedBy(...) + CompleteConfigureAsync), so the implicit root folder is no longer needed as the anchor for build-created nodes.

Proposal

  1. 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).
  2. 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.
  3. Move the ReferenceServer folder creation into AddReferenceServer, which is the only place that name belongs.
  4. 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*).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugA bug was identified and should be fixed.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions