Skip to content

Server-side complex types via the shared native-AOT path (#1306)#3955

Open
marcschier wants to merge 6 commits into
OPCFoundation:masterfrom
marcschier:marcschier/server-complex-types
Open

Server-side complex types via the shared native-AOT path (#1306)#3955
marcschier wants to merge 6 commits into
OPCFoundation:masterfrom
marcschier:marcschier/server-complex-types

Conversation

@marcschier

Copy link
Copy Markdown
Collaborator

Description

Enable an OPC UA server to build dynamic stand-in encodeables for the custom DataTypes it loaded from a NodeSet at runtime, so a Variant / ExtensionObject carrying a custom type "just works" without a compiled .NET type. The server reuses exactly the same NativeAOT-friendly path as the client (ComplexTypeSystem) rather than a parallel implementation, and does not touch the legacy reflection-emit builder.

What changed

Shared machinery moved to a common assembly

  • Moved ComplexTypeSystem, the resolver/factory interfaces, the DefaultComplexType* builder and DataDictionary from Opc.Ua.Client to Opc.Ua.Core.Schema (namespace Opc.Ua.Schema) so both client and server use it (git history preserved via renames).
  • The client keeps NodeCacheResolver and gains ComplexTypeSystem.Create(session, …) helpers; the ISession constructors are replaced by those helpers (the reflection-emit builder is opt-in via Create(session, new ComplexTypeBuilderFactory(), telemetry)).

Server-side complex types

  • AddressSpaceComplexTypeResolver reads the server's in-memory address space (DataType nodes + DataTypeDefinition).
  • Opt-in fluent/DI AddComplexTypeSystem(), a ready-made ComplexTypeStandardServer, and IServerInternal.LoadComplexTypesAsync(…). A new async StandardServer.OnNodeManagerStartedAsync hook runs the pass after the address space is built and before endpoints open. DataTypes already backed by a compiled/generated type are skipped; only runtime-loaded ones become stand-ins.

Encodeable factory as the source of truth for data type definitions

  • The runtime stand-ins (Structure / Enumeration / OptionSet) now implement IDataTypeDefinitionSource, so EncodeableFactoryDefinitionSource resolves generated and runtime types. The server exposes the primed factory as the schema IDataTypeDefinitionResolver; DataTypeDefinitionRegistry is kept only as an optional supplement for schema-only types (composed via CompositeDataTypeDefinitionResolver). No address-space walk or separate registry population is required.

Tests

  • New ServerComplexTypeSystemTests (factory priming by type + encoding id, skip-known, factory-backed resolver + composite fallthrough, DI registration) and new stand-in resolution tests in EncodeableFactoryDefinitionSourceTests.
  • Verified locally on .NET Framework 4.8 and .NET 10: Opc.Ua.Core.Schema.Tests (67), the server complex-type tests, and 2228 client complex-type tests pass; net48 + net10 builds are 0‑warning.

Docs

  • Docs/ComplexTypes.md (new server section), Docs/SchemaGeneration.md (factory-as-resolver), and Docs/migrate/2.0.x/encoders.md.

Related Issues

Checklist

Put an x in the boxes that apply. You can complete these step by step after opening the PR.

  • I have signed the CLA and read the CONTRIBUTING doc.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added all necessary documentation.
  • 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. (Ran the affected complex-type / schema / server suites on net48 + net10; full UA.slnx run pending CI.)
  • I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings.
  • I have addressed all PR feedback received.

…undation#1306)

Enable an OPC UA server to build dynamic stand-in encodeables for the custom
DataTypes it loaded from a NodeSet at runtime, reusing exactly the same
NativeAOT-friendly path as the client (ComplexTypeSystem) instead of the legacy
reflection-emit builder.

- Move the shared complex-type machinery (ComplexTypeSystem, the resolver/
  factory interfaces, DefaultComplexType* builder, DataDictionary) from
  Opc.Ua.Client to Opc.Ua.Core.Schema (namespace Opc.Ua.Schema) so both client
  and server use it. The client keeps NodeCacheResolver and the
  ComplexTypeSystem.Create(session, ...) helpers; the ISession constructors are
  replaced by those Create helpers.
- Add a server-side AddressSpaceComplexTypeResolver over the in-memory address
  space, an opt-in fluent/DI AddComplexTypeSystem(), a ready-made
  ComplexTypeStandardServer, and IServerInternal.LoadComplexTypesAsync(). The
  pass runs after the address space is built and before endpoints open, and
  skips DataTypes already known to the factory (generated types).
- Make the runtime stand-ins (Structure/Enumeration/OptionSet) implement
  IDataTypeDefinitionSource so the encodeable factory is the source of truth for
  data type definitions. The server exposes the primed factory as the schema
  IDataTypeDefinitionResolver via EncodeableFactoryDefinitionSource; the
  DataTypeDefinitionRegistry is kept only as an optional supplement for
  schema-only types (composed via CompositeDataTypeDefinitionResolver).
- Add unit/integration tests and update Docs/ComplexTypes.md,
  Docs/SchemaGeneration.md and the 2.0 migration guide.
@CLAassistant

CLAassistant commented Jul 4, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ marcschier
❌ Copilot
You have signed the CLA already but the status is still pending? Let us recheck it.

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.82938% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.52%. Comparing base (2b4b067) to head (8f2ed55).

Files with missing lines Patch % Lines
...er/ComplexTypes/AddressSpaceComplexTypeResolver.cs 89.76% 4 Missing and 9 partials ⚠️
...r/ComplexTypes/ServerDataTypeDefinitionResolver.cs 0.00% 13 Missing ⚠️
.../Opc.Ua.Server/Hosting/OpcUaServerHostedService.cs 0.00% 11 Missing ⚠️
....Ua.Server/ComplexTypes/ServerComplexTypeSystem.cs 80.95% 2 Missing and 2 partials ⚠️
...osting/OpcUaServerComplexTypesBuilderExtensions.cs 76.47% 1 Missing and 3 partials ⚠️
...ma/Resolution/EncodeableFactoryDefinitionSource.cs 0.00% 3 Missing ⚠️
...Ua.Client.ComplexTypes/ComplexTypeSystemFactory.cs 0.00% 1 Missing ⚠️
.../ComplexTypes/ComplexTypeSystemClientExtensions.cs 66.66% 1 Missing ⚠️
Libraries/Opc.Ua.Server/Server/StandardServer.cs 91.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3955      +/-   ##
==========================================
+ Coverage   73.51%   73.52%   +0.01%     
==========================================
  Files        1170     1174       +4     
  Lines      170175   170371     +196     
  Branches    29363    29406      +43     
==========================================
+ Hits       125097   125270     +173     
- Misses      34072    34086      +14     
- Partials    11006    11015       +9     
Files with missing lines Coverage Δ
...c.Ua.Core.Schema/ComplexTypes/ComplexTypeSystem.cs 51.37% <ø> (ø)
.../Opc.Ua.Core.Schema/ComplexTypes/DataDictionary.cs 57.89% <ø> (ø)
...Schema/ComplexTypes/DataTypeDefinitionExtension.cs 0.00% <ø> (ø)
...c.Ua.Core.Schema/ComplexTypes/DataTypeException.cs 0.00% <ø> (ø)
...e.Schema/ComplexTypes/DefaultComplexTypeBuilder.cs 100.00% <ø> (ø)
...e.Schema/ComplexTypes/DefaultComplexTypeFactory.cs 88.88% <ø> (ø)
...ema/ComplexTypes/DefaultComplexTypeFieldBuilder.cs 77.19% <ø> (ø)
Stack/Opc.Ua.Core.Types/Types/OptionSet.cs 67.64% <100.00%> (+0.32%) ⬆️
Stack/Opc.Ua.Types/Encoders/Enumeration.cs 89.47% <100.00%> (+0.58%) ⬆️
Stack/Opc.Ua.Types/Encoders/Structure.cs 51.93% <100.00%> (+0.37%) ⬆️
... and 9 more

... and 21 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread Docs/migrate/2.0.x/encoders.md Outdated
Comment thread Docs/ComplexTypes.md Outdated
Comment thread Docs/ComplexTypes.md
Comment thread Docs/SchemaGeneration.md Outdated
Comment thread Libraries/Opc.Ua.Server/ComplexTypes/ComplexTypeStandardServer.cs Outdated
Addresses review feedback on OPCFoundation#3955:

- Integrate runtime complex-type loading as the default in StandardServer via
  a new public LoadComplexTypes flag (opt out by setting it false), removing the
  ComplexTypeStandardServer subclass. The base OnNodeManagerStartedAsync now runs
  the pass and exposes the primed factory as the schema resolver.
- OpcUaServerHostedService constructs a plain StandardServer and sets the
  complex-type options/registry/resolver-holder; AddComplexTypeSystem() now
  configures (and can opt out via ServerComplexTypeOptions.Enabled) rather than
  enabling the feature.
- Docs: clarify that compiled/source-generated types are already registered in
  the IEncodeableFactory and used as-is, describe how and when generated types
  reach the factory, note built-in primitive types carry no DataTypeDefinition,
  and document the default-on behavior/opt-out.
@marcschier marcschier marked this pull request as ready for review July 4, 2026 15:59
Copilot AI review requested due to automatic review settings July 4, 2026 15:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Enables OPC UA servers to build and register dynamic stand-in encodeables for runtime-loaded custom DataTypes by reusing the shared NativeAOT-friendly ComplexTypeSystem path (now shared in Opc.Ua.Core.Schema), and exposes the primed encodeable factory as the schema definition source.

Changes:

  • Adds server-side complex type loading during StandardServer startup (pre-endpoint open) plus DI configuration via AddComplexTypeSystem().
  • Makes runtime stand-ins (Structure/Enumeration/OptionSet) expose their DataTypeDefinition via IDataTypeDefinitionSource and updates factory-backed resolution/deduping.
  • Adds/updates unit tests across server and schema layers and migrates usages to the new shared Opc.Ua.Schema namespace.

Reviewed changes

Copilot reviewed 47 out of 48 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Tests/Opc.Ua.Server.Tests/ServerComplexTypeSystemTests.cs Adds server-side complex type loading tests (factory priming, skipping known, DI wiring).
Tests/Opc.Ua.Core.Schema.Tests/EncodeableFactoryDefinitionSourceTests.cs Adds coverage for resolving DataTypeDefinition from runtime stand-ins via factory source.
Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/SchemaRegistrationTests.cs Updates construction to new ComplexTypeSystem API after move to Opc.Ua.Schema.
Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs Updates tests to use new ComplexTypeSystem constructor and schema namespace imports.
Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs Updates imports to Opc.Ua.Schema.
Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs Updates imports to Opc.Ua.Schema.
Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs Updates session-bound ComplexTypeSystem.Create(...) calls to new overloads.
Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs Updates imports to Opc.Ua.Schema.
Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs Updates imports to Opc.Ua.Schema.
Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs Updates imports to Opc.Ua.Schema.
Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs Updates imports to Opc.Ua.Schema.
Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs Updates imports to Opc.Ua.Schema.
Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs Updates imports to Opc.Ua.Schema.
Tests/Opc.Ua.Aot.Tests/ComplexTypeAotTests.cs Switches to ComplexTypeSystem.Create(...) session helper usage.
Stack/Opc.Ua.Types/Encoders/Structure.cs Implements IDataTypeDefinitionSource on runtime Structure stand-in.
Stack/Opc.Ua.Types/Encoders/Enumeration.cs Implements IDataTypeDefinitionSource on runtime Enumeration stand-in.
Stack/Opc.Ua.Core.Types/Types/OptionSet.cs Implements IDataTypeDefinitionSource on runtime OptionSet stand-in.
Stack/Opc.Ua.Core.Schema/Resolution/EncodeableFactoryDefinitionSource.cs De-duplicates factory-known types when listing namespace types.
Stack/Opc.Ua.Core.Schema/Opc.Ua.Core.Schema.csproj Adds InternalsVisibleTo for client and complex-types tests.
Libraries/Opc.Ua.Server/Server/StandardServer.cs Adds async startup hook to load complex types before accepting connections.
Libraries/Opc.Ua.Server/Hosting/OpcUaServerHostedService.cs Wires server complex type options/registry/resolver holder via DI.
Libraries/Opc.Ua.Server/Hosting/OpcUaServerComplexTypesBuilderExtensions.cs Adds AddComplexTypeSystem() DI extension to configure/load resolver.
Libraries/Opc.Ua.Server/ComplexTypes/ServerDataTypeDefinitionResolver.cs Adds mutable DI resolver holder for late-bound factory-backed resolver.
Libraries/Opc.Ua.Server/ComplexTypes/ServerComplexTypeSystem.cs Adds IServerInternal.LoadComplexTypesAsync(...) implementation.
Libraries/Opc.Ua.Server/ComplexTypes/ServerComplexTypeOptions.cs Introduces server load options (Enabled, OnlyEnumTypes, ThrowOnError).
Libraries/Opc.Ua.Server/ComplexTypes/AddressSpaceComplexTypeResolver.cs Adds resolver over server in-memory address space for shared ComplexTypeSystem.
Libraries/Opc.Ua.Client/ComplexTypes/NodeCacheResolver.cs Updates imports to Opc.Ua.Schema.
Libraries/Opc.Ua.Client/ComplexTypes/IComplexTypeResolver.cs Moves interface namespace to Opc.Ua.Schema.
Libraries/Opc.Ua.Client/ComplexTypes/IComplexTypeFactory.cs Moves interface namespace to Opc.Ua.Schema.
Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeFieldBuilder.cs Moves implementation namespace to Opc.Ua.Schema.
Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeFactory.cs Moves implementation namespace to Opc.Ua.Schema.
Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeBuilder.cs Moves implementation namespace to Opc.Ua.Schema.
Libraries/Opc.Ua.Client/ComplexTypes/DataTypeException.cs Moves exception namespace to Opc.Ua.Schema.
Libraries/Opc.Ua.Client/ComplexTypes/DataTypeDefinitionExtension.cs Moves extension namespace to Opc.Ua.Schema.
Libraries/Opc.Ua.Client/ComplexTypes/DataDictionary.cs Moves type namespace to Opc.Ua.Schema.
Libraries/Opc.Ua.Client/ComplexTypes/ComplexTypeSystem.cs Moves ComplexTypeSystem to Opc.Ua.Schema and removes ISession ctors.
Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFieldBuilder.cs Updates imports to Opc.Ua.Schema.
Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFactory.cs Updates imports to Opc.Ua.Schema.
Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs Updates imports to Opc.Ua.Schema.
Libraries/Opc.Ua.Client.ComplexTypes/OpcUaComplexTypesBuilderExtensions.cs Updates imports to Opc.Ua.Schema.
Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs Renames/reworks session-bound Create(...) helpers after shared move.
Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystemFactory.cs Updates to build resolver from session node cache explicitly.
Docs/migrate/2.0.x/encoders.md Updates migration guidance for schema assembly move + new constructors.
Docs/SchemaGeneration.md Documents factory-backed IDataTypeDefinitionResolver usage via IDataTypeDefinitionSource.
Docs/ComplexTypes.md Updates client guidance and adds server-side complex type loading section.
Applications/ConsoleReferenceClient/Program.cs Updates sample to use ComplexTypeSystem.Create(session, telemetry).
Applications/ConsoleReferenceClient/ClientSamples.cs Updates imports to Opc.Ua.Schema.
Comments suppressed due to low confidence (2)

Tests/Opc.Ua.Server.Tests/ServerComplexTypeSystemTests.cs:1

  • [Parallelizable] on a fixture that uses shared mutable instance fields (e.g., m_factory, m_nodesById, mocks set up in [SetUp]) can cause test interference because NUnit typically reuses one fixture instance across tests. To avoid race conditions/flaky tests, either remove [Parallelizable] here or add [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] (and keep per-test state isolated).
    Stack/Opc.Ua.Core.Schema/Resolution/EncodeableFactoryDefinitionSource.cs:1
  • De-duplicating GetNamespaceTypes(...) by (description.Name, description.NamespaceUri) can incorrectly drop distinct types that share a BrowseName within the same namespace (BrowseName uniqueness is not guaranteed). A safer de-dup key is the resolved type identifier (e.g., description.TypeId / the canonical data type id) rather than the qualified name.

Comment thread Libraries/Opc.Ua.Server/Hosting/OpcUaServerComplexTypesBuilderExtensions.cs Outdated
Addresses review feedback on OPCFoundation#3955 (namespace thread): move the shared
complex-type types (ComplexTypeSystem, IComplexType* interfaces, default
builder/factory, DataDictionary, DataTypeDefinitionExtension, DataType*Exception)
from Opc.Ua.Schema to the root Opc.Ua namespace. Opc.Ua is already imported by
consumers, so existing code keeps compiling without adding a new using; the
pre-existing schema resolution/generation types stay in Opc.Ua.Schema.

- Renamespaced the 9 moved files to Opc.Ua; ComplexTypeSystem/DataDictionary add
  'using Opc.Ua.Schema;' for the remaining registry/validator types.
- Removed the now-unused 'using Opc.Ua.Schema;' from complex-type consumers
  (client, client.complextypes, server resolver, samples, tests); kept it where
  remaining Opc.Ua.Schema types are still used.
- Updated ComplexTypes.md and migrate/2.0.x/encoders.md namespace references and
  migration guidance.

Verified: Core.Schema/Client/Client.ComplexTypes/Server + ConsoleReferenceClient
build clean; 2729 Client.ComplexTypes.Tests and server complex-type/schema tests pass.
Copilot AI added 2 commits July 4, 2026 18:54
…ror context (PR feedback)

Addresses 3 review comments on OPCFoundation#3955:

- AddressSpaceComplexTypeResolver.GetEnumTypeArrayAsync no longer returns the
  first non-null HasProperty target (which could be an unrelated property).
  It now matches the well-known EnumValues/EnumStrings/OptionSetValues by
  BrowseName and selects them in an explicit priority order.
- LoadDataTypesAsync throws a ServiceResultException with a meaningful OPC UA
  StatusCode (BadNodeIdUnknown when missing, BadNodeClassInvalid otherwise) and
  includes the node id in the message, instead of a generic message.
- AddComplexTypeSystem() registers IDataTypeDefinitionResolver via
  TryAddSingleton (matching AddSchemaGeneration), keeping registration
  idempotent across multiple/transitive calls.
- Added ServerComplexTypeSystemTests covering the property-selection fix
  (ignores an unrelated property; prefers EnumValues over EnumStrings).
…s branch

Improves changed-line coverage for the server complex-type feature and removes
dead code:

- AddressSpaceComplexTypeResolver.LoadDataTypesAsync: FindAsync only ever returns
  a DataTypeNode or null, so the BadNodeClassInvalid branch was unreachable.
  Simplified to a single BadNodeIdUnknown throw with a clearer message.
- Added ServerComplexTypeSystemTests covering the previously untested resolver
  methods: LoadDataTypeSystem, BrowseTypeIdsForDictionaryComponentAsync,
  FindSuperTypeAsync, FindAsync (DataType and non-DataType), both
  BrowseForEncodingsAsync overloads, and LoadDataTypesAsync (custom subtypes,
  add-root-node, and the not-a-DataType throw path).

All 15 ServerComplexTypeSystemTests pass; Opc.Ua.Server builds clean.
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.

Support for Complex types in server (Opc.Ua.Server.ComplexTypes)

4 participants