diff --git a/Applications/ConsoleReferenceClient/ClientSamples.cs b/Applications/ConsoleReferenceClient/ClientSamples.cs
index c8a38c01ce..a1c8b0343a 100644
--- a/Applications/ConsoleReferenceClient/ClientSamples.cs
+++ b/Applications/ConsoleReferenceClient/ClientSamples.cs
@@ -41,6 +41,7 @@
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Client;
+using Opc.Ua.ComplexTypes;
using Opc.Ua.Client.ComplexTypes;
namespace Quickstarts
diff --git a/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj b/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj
index 1768a9b8dd..0d930c338c 100644
--- a/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj
+++ b/Applications/ConsoleReferenceClient/ConsoleReferenceClient.csproj
@@ -30,6 +30,7 @@
+
diff --git a/Applications/ConsoleReferenceClient/Program.cs b/Applications/ConsoleReferenceClient/Program.cs
index 1b2351b795..9c1b4d567c 100644
--- a/Applications/ConsoleReferenceClient/Program.cs
+++ b/Applications/ConsoleReferenceClient/Program.cs
@@ -40,6 +40,7 @@
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Client;
+using Opc.Ua.ComplexTypes;
using Opc.Ua.Client.ComplexTypes;
using Opc.Ua.Configuration;
using Opc.Ua.Security.Certificates;
diff --git a/BreakingChanges.md b/BreakingChanges.md
new file mode 100644
index 0000000000..0a396b5340
--- /dev/null
+++ b/BreakingChanges.md
@@ -0,0 +1,288 @@
+# Breaking Changes: ComplexTypes Refactor
+
+This document accumulates the breaking changes from the multi-wave
+ComplexTypes refactor. **Wave 1** split the legacy mono-package into a
+three-project architecture; **Wave 2** isolated the Reflection.Emit path
+into a separate opt-in package so AOT-published consumers can omit it.
+
+---
+
+# Wave 1: Three-Project Split
+
+## 1. Namespace moves (the big one)
+
+Every type listed below moved from `Opc.Ua.Client.ComplexTypes` to **`Opc.Ua.ComplexTypes`**. Consumers must add `using Opc.Ua.ComplexTypes;` (in addition to or in place of `using Opc.Ua.Client.ComplexTypes;`, depending on what they touch).
+
+**Interfaces:**
+- `IComplexTypeResolver`
+- `IComplexTypeFactory`
+- `IComplexTypeBuilder`
+- `IComplexTypeFieldBuilder`
+
+**Default (AOT-friendly) implementations:**
+- `DefaultComplexTypeFactory`
+- `DefaultComplexTypeBuilder`
+- `DefaultComplexTypeFieldBuilder`
+
+**Reflection.Emit implementations:**
+- `ComplexTypeBuilderFactory`
+- `ComplexTypeBuilder`
+- `ComplexTypeFieldBuilder`
+- `AssemblyModule`
+- `AttributeExtensions`
+
+**Runtime base types and attributes:**
+- `BaseComplexType`
+- `OptionalFieldsComplexType`
+- `UnionComplexType`
+- `ComplexTypePropertyInfo`
+- `IComplexTypeProperties`
+- `StructureDefinitionAttribute`
+- `StructureFieldAttribute`
+- `StructureTypeIdAttribute`
+- `StructureBaseDataType` (enum)
+
+**Schema / dictionary:**
+- `DataDictionary`
+- `DataTypeDefinitionExtension`
+
+**Exceptions:**
+- `DataTypeNotFoundException`
+- `DataTypeNotSupportedException`
+
+## 2. Types that stayed in `Opc.Ua.Client.ComplexTypes` namespace but moved assemblies
+
+These were previously compiled into `Opc.Ua.Client.dll`; they are now in `Opc.Ua.Client.ComplexTypes.dll`. Source code that only uses `using Opc.Ua.Client.ComplexTypes;` keeps working, but **reflection-based** or **assembly-qualified-name** consumers will break:
+
+- `ComplexTypeSystem`
+- `NodeCacheResolver`
+
+## 3. Assembly redistribution
+
+| Type | Was in | Now in |
+| --- | --- | --- |
+| Interfaces, default builders, `DataDictionary`, `DataTypeDefinitionExtension`, exceptions | `Opc.Ua.Client.dll` | **`Opc.Ua.ComplexTypes.dll`** (new) |
+| Reflection.Emit builders, runtime base types, attributes | `Opc.Ua.Client.ComplexTypes.dll` | **`Opc.Ua.ComplexTypes.dll`** (new) |
+| `ComplexTypeSystem`, `NodeCacheResolver` | `Opc.Ua.Client.dll` | `Opc.Ua.Client.ComplexTypes.dll` |
+
+Reflection by `Type.AssemblyQualifiedName`, `Assembly.GetType("Opc.Ua.Client.ComplexTypes.X, Opc.Ua.Client")`, or strong-name-pinned loaders will fail.
+
+## 4. New required dependencies
+
+- Anyone consuming the moved types via source code needs the new `OPCFoundation.NetStandard.Opc.Ua.ComplexTypes` NuGet package. It comes transitively when you depend on `OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes`, so most consumers won't notice — but **projects that previously got the interfaces and `Default*` builders from `Opc.Ua.Client` alone now need to add the new package explicitly**, because those types no longer ship inside `Opc.Ua.Client.dll`.
+
+## 5. No backward-compatibility shims
+
+The original plan called for `[assembly: TypeForwardedTo(...)]` to forward the old type identities. **This was intentionally not done.** Consequence:
+
+- Source code must update `using` statements.
+- Binary consumers compiled against the old layout (1.5.x or earlier `Opc.Ua.Client.dll` / `Opc.Ua.Client.ComplexTypes.dll`) will throw `TypeLoadException` at runtime when they touch any moved type. Recompile against the new layout.
+
+## 6. `InternalsVisibleTo` changes
+
+- `Opc.Ua.Client.ComplexTypes.csproj` now also grants internals to `Opc.Ua.Client.Tests` (because `NodeCacheResolver`'s `internal` extension methods like `LoadDictionaryAsync` / `ReadDictionaryAsync` moved out of `Opc.Ua.Client`).
+- `Opc.Ua.ComplexTypes.csproj` grants internals to `Opc.Ua.Client`, `Opc.Ua.Client.ComplexTypes`, `Opc.Ua.Server.ComplexTypes`, and the corresponding test assemblies.
+
+External assemblies that previously relied on `InternalsVisibleTo` on the old `Opc.Ua.Client` or `Opc.Ua.Client.ComplexTypes` for any of the moved types will need that grant restated on `Opc.Ua.ComplexTypes`.
+
+## 7. Net-new public surface (additive, not breaking — listed for completeness)
+
+- `Opc.Ua.Server.ComplexTypes.ServerComplexTypeSystem` (`RegisterEnumeration`, `RegisterStructure`, `Flush`)
+- `Opc.Ua.Server.ComplexTypes.ServerComplexTypeSystemFactory`
+- `Microsoft.Extensions.DependencyInjection.OpcUaComplexTypesServerBuilderExtensions.AddComplexTypes(this IOpcUaServerBuilder)`
+
+## 8. Recommended migration path
+
+```csharp
+// Before
+using Opc.Ua.Client.ComplexTypes;
+
+IComplexTypeFactory factory = new ComplexTypeBuilderFactory();
+var typeSystem = new ComplexTypeSystem(session);
+```
+
+```csharp
+// After
+using Opc.Ua.ComplexTypes; // interfaces, builders, DataDictionary, exceptions, attributes, base types
+using Opc.Ua.Client.ComplexTypes; // ComplexTypeSystem, NodeCacheResolver, DI extension
+
+IComplexTypeFactory factory = new ComplexTypeBuilderFactory();
+var typeSystem = new ComplexTypeSystem(session);
+```
+
+Drop a single new `using Opc.Ua.ComplexTypes;` at the top of the file and most code compiles unchanged.
+
+---
+
+# Wave 2: AOT / Reflection.Emit Split
+
+The single `Opc.Ua.ComplexTypes` library produced by Wave 1 mixed an
+AOT-friendly core (interfaces, `Default*` builders, `DataDictionary`,
+`DataTypeDefinitionExtension`, exceptions) with a Reflection.Emit code
+path (the `ComplexTypeBuilder*` trio, runtime base classes, runtime
+attributes). Wave 2 extracts the Emit path into its own NuGet package so
+that AOT-published apps never load the Emit code and trim-safe analysis
+keeps working.
+
+## 1. Namespace moves (from `Opc.Ua.ComplexTypes` to **`Opc.Ua.ComplexTypes.Emit`**)
+
+**Reflection.Emit builders:**
+- `ComplexTypeBuilder`
+- `ComplexTypeBuilderFactory`
+- `ComplexTypeFieldBuilder`
+- `AssemblyModule`
+- `AttributeExtensions`
+
+**Runtime base types and helpers (only used as bases for Emit-generated classes):**
+- `BaseComplexType`
+- `OptionalFieldsComplexType`
+- `UnionComplexType`
+- `ComplexTypePropertyInfo`
+- `IComplexTypeProperties`
+
+**Attributes (consumed via reflection on Emit-generated types):**
+- `StructureDefinitionAttribute`
+- `StructureFieldAttribute`
+- `StructureTypeIdAttribute`
+- `StructureBaseDataType` (enum)
+
+Consumers add `using Opc.Ua.ComplexTypes.Emit;` to keep these symbols
+visible.
+
+## 2. Assembly redistribution
+
+| Type | Was in (after Wave 1) | Now in |
+| --- | --- | --- |
+| Emit builders + base types + attributes | `Opc.Ua.ComplexTypes.dll` | **`Opc.Ua.ComplexTypes.Emit.dll`** (new) |
+| `IComplexType*`, `DefaultComplexType*`, `DataDictionary`, `DataTypeDefinitionExtension`, exceptions | `Opc.Ua.ComplexTypes.dll` | `Opc.Ua.ComplexTypes.dll` (unchanged) |
+
+Reflection by `Type.AssemblyQualifiedName` against any moved type
+breaks; recompile.
+
+## 3. New required NuGet package (opt-in)
+
+`OPCFoundation.NetStandard.Opc.Ua.ComplexTypes.Emit` is the new
+opt-in package. It is **not** pulled in transitively by
+`Opc.Ua.Client.ComplexTypes` — that package now only depends on the
+AOT-friendly core. Apps that need runtime concrete .NET classes for
+custom DataTypes must add the Emit package explicitly.
+
+Server hosts (`Opc.Ua.Server.ComplexTypes`) are unaffected by default —
+they use `DefaultComplexTypeFactory`. To use the Emit factory on the
+server side, add the Emit package and pass
+`() => new ComplexTypeBuilderFactory()` to the
+`ServerComplexTypeSystemFactory(ITelemetryContext, Func)`
+constructor.
+
+## 4. DI default flip — **behavioural break**
+
+| Call | Before (Wave 1) | After (Wave 2) |
+| --- | --- | --- |
+| `IOpcUaBuilder.AddComplexTypes()` | Registers `ComplexTypeSystemFactory` bound to **`ComplexTypeBuilderFactory`** (Reflection.Emit). | Registers `ComplexTypeSystemFactory` bound to **`DefaultComplexTypeFactory`** (AOT-friendly). |
+| `IOpcUaServerBuilder.AddComplexTypes()` | Same flip — server factory now defaults to `DefaultComplexTypeFactory`. | Same flip. |
+
+To restore the Wave 1 behaviour, add the Emit package and call the new
+opt-in extension:
+
+```csharp
+// Wave 1 (AddComplexTypes used Emit by default)
+services.AddOpcUa().AddComplexTypes();
+
+// Wave 2 — explicit Emit
+services.AddOpcUa().AddComplexTypesWithReflectionEmit();
+```
+
+`AddComplexTypesWithReflectionEmit()` is shipped by the
+`Opc.Ua.ComplexTypes.Emit` package in the
+`Microsoft.Extensions.DependencyInjection` namespace and replaces the
+default registration via `ServiceCollectionDescriptorExtensions.Replace`.
+
+## 5. Static factory rename
+
+The `ComplexTypeSystem.Create(...)` extension members shipped by the
+old `ComplexTypesExtensions` (in `Opc.Ua.Client.ComplexTypes`) have
+been **removed** and replaced by `CreateWithReflectionEmit(...)`
+extension members shipped by the new
+`Opc.Ua.ComplexTypes.Emit.ComplexTypesEmitExtensions`. The signatures
+are otherwise identical:
+
+```csharp
+// Before
+using Opc.Ua.Client.ComplexTypes;
+var ts = ComplexTypeSystem.Create(session, telemetry);
+var ts2 = ComplexTypeSystem.Create(resolver, telemetry);
+
+// After (still want Emit)
+using Opc.Ua.ComplexTypes.Emit;
+var ts = ComplexTypeSystem.CreateWithReflectionEmit(session, telemetry);
+var ts2 = ComplexTypeSystem.CreateWithReflectionEmit(resolver, telemetry);
+
+// Or (use the AOT-friendly default)
+var ts = new ComplexTypeSystem(session); // already used DefaultComplexTypeFactory in Wave 1
+```
+
+## 6. `ComplexTypeSystemFactory` / `ServerComplexTypeSystemFactory` ctor surface
+
+Both DI factories gained a second constructor that accepts a
+`Func` delegate. The original single-arg ctor
+keeps the AOT-friendly behaviour:
+
+```csharp
+// AOT-friendly (Wave 2 default)
+new ComplexTypeSystemFactory(telemetry);
+
+// Emit (manual wiring, e.g. on the server side without the DI extension)
+new ServerComplexTypeSystemFactory(
+ telemetry,
+ static () => new ComplexTypeBuilderFactory());
+```
+
+This is additive at the source level (existing single-arg call sites
+still compile) but is a behavioural break in combination with §4 — the
+factory the single-arg ctor produces now defaults to
+`DefaultComplexTypeFactory`.
+
+## 7. `InternalsVisibleTo` shifts
+
+- `Opc.Ua.ComplexTypes.Emit.csproj` grants internals to
+ `Opc.Ua.Client.ComplexTypes`, `Opc.Ua.Server.ComplexTypes`, and
+ `Opc.Ua.Client.ComplexTypes.Tests`.
+- `Opc.Ua.ComplexTypes.csproj`'s grants are unchanged but the practical
+ surface area exposed by them is smaller because most consumers of
+ `internal` members now live in `Opc.Ua.ComplexTypes.Emit`.
+
+External assemblies that relied on `InternalsVisibleTo` against
+`Opc.Ua.ComplexTypes` for any of the moved Emit types need to restate
+the grant against `Opc.Ua.ComplexTypes.Emit`.
+
+## 8. Recommended migration path
+
+```csharp
+// Before (Wave 1)
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.Client.ComplexTypes;
+
+services.AddOpcUa().AddComplexTypes(); // got Emit factory
+var ts = ComplexTypeSystem.Create(session, telemetry);
+IComplexTypeFactory factory = new ComplexTypeBuilderFactory();
+```
+
+```csharp
+// After (Wave 2) — keep Emit behaviour
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.ComplexTypes.Emit;
+using Opc.Ua.Client.ComplexTypes;
+
+services.AddOpcUa().AddComplexTypesWithReflectionEmit(); // explicit Emit opt-in
+var ts = ComplexTypeSystem.CreateWithReflectionEmit(session, telemetry);
+IComplexTypeFactory factory = new ComplexTypeBuilderFactory(); // namespace changed
+```
+
+```csharp
+// After (Wave 2) — accept the AOT-friendly default
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.Client.ComplexTypes;
+
+services.AddOpcUa().AddComplexTypes(); // now DefaultComplexTypeFactory
+var ts = new ComplexTypeSystem(session);
+```
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/ComplexTypeSystem.cs b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client/ComplexTypes/ComplexTypeSystem.cs
rename to Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs
index 3f6672f9b0..24e45b4ba2 100644
--- a/Libraries/Opc.Ua.Client/ComplexTypes/ComplexTypeSystem.cs
+++ b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs
@@ -35,6 +35,7 @@
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Extensions.Logging;
+using Opc.Ua.ComplexTypes;
namespace Opc.Ua.Client.ComplexTypes
{
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystemFactory.cs b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystemFactory.cs
index 5bc75e2a0a..2be55868ec 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystemFactory.cs
+++ b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystemFactory.cs
@@ -28,6 +28,7 @@
* ======================================================================*/
using System;
+using Opc.Ua.ComplexTypes;
namespace Opc.Ua.Client.ComplexTypes
{
@@ -39,21 +40,48 @@ namespace Opc.Ua.Client.ComplexTypes
///
///
/// Registered as a singleton by
- /// IOpcUaBuilder.AddComplexTypes(). Consumers (typically
- /// ManagedSession hosts) resolve this factory and call
- /// per session to obtain a
- /// type-loader scoped to that session.
+ /// IOpcUaBuilder.AddComplexTypes(). By default the factory
+ /// produces type loaders backed by the AOT-friendly
+ /// . Hosts that need
+ /// runtime concrete .NET classes for custom DataTypes register the
+ /// Reflection.Emit-based ComplexTypeBuilderFactory from
+ /// Opc.Ua.ComplexTypes.Emit via the
+ /// AddComplexTypesWithReflectionEmit() builder extension —
+ /// it swaps the registered factory descriptor for this one.
///
public sealed class ComplexTypeSystemFactory
{
///
- /// Initializes a new instance.
+ /// Initializes a new instance backed by
+ /// .
///
/// The shared telemetry context.
/// is null.
public ComplexTypeSystemFactory(ITelemetryContext telemetry)
+ : this(telemetry, static () => new DefaultComplexTypeFactory())
+ {
+ }
+
+ ///
+ /// Initializes a new instance backed by a caller-supplied
+ /// source. The
+ /// delegate is
+ /// invoked once per call so each
+ /// gets its own builder
+ /// factory.
+ ///
+ /// The shared telemetry context.
+ /// Delegate that
+ /// produces a fresh per
+ /// session.
+ /// Any argument is null.
+ public ComplexTypeSystemFactory(
+ ITelemetryContext telemetry,
+ Func complexTypeFactoryFactory)
{
m_telemetry = telemetry ?? throw new ArgumentNullException(nameof(telemetry));
+ m_complexTypeFactoryFactory = complexTypeFactoryFactory ??
+ throw new ArgumentNullException(nameof(complexTypeFactoryFactory));
}
///
@@ -73,10 +101,11 @@ public ComplexTypeSystem Create(ISession session)
}
return new ComplexTypeSystem(
session,
- new ComplexTypeBuilderFactory(),
+ m_complexTypeFactoryFactory(),
m_telemetry);
}
private readonly ITelemetryContext m_telemetry;
+ private readonly Func m_complexTypeFactoryFactory;
}
}
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/NodeCacheResolver.cs b/Libraries/Opc.Ua.Client.ComplexTypes/NodeCacheResolver.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client/ComplexTypes/NodeCacheResolver.cs
rename to Libraries/Opc.Ua.Client.ComplexTypes/NodeCacheResolver.cs
index 82a693b852..2f506373b1 100644
--- a/Libraries/Opc.Ua.Client/ComplexTypes/NodeCacheResolver.cs
+++ b/Libraries/Opc.Ua.Client.ComplexTypes/NodeCacheResolver.cs
@@ -35,6 +35,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Opc.Ua.ComplexTypes;
namespace Opc.Ua.Client.ComplexTypes
{
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj b/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj
index 4c9f9d02fa..f971105a01 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj
+++ b/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj
@@ -12,6 +12,7 @@
+
$(PackageId).Debug
@@ -19,6 +20,7 @@
+
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/readme.md b/Libraries/Opc.Ua.Client.ComplexTypes/readme.md
similarity index 100%
rename from Libraries/Opc.Ua.Client/ComplexTypes/readme.md
rename to Libraries/Opc.Ua.Client.ComplexTypes/readme.md
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AssemblyModule.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/AssemblyModule.cs
similarity index 98%
rename from Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AssemblyModule.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Builders/AssemblyModule.cs
index 2eb7bf7792..5589ef2f63 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AssemblyModule.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/AssemblyModule.cs
@@ -32,7 +32,7 @@
using System.Reflection;
using System.Reflection.Emit;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// Use a single assembly and module builder instance to build the type system.
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AttributeExtensions.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/AttributeExtensions.cs
similarity index 98%
rename from Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AttributeExtensions.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Builders/AttributeExtensions.cs
index ccb45547ce..dc0225b18f 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AttributeExtensions.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/AttributeExtensions.cs
@@ -35,7 +35,7 @@
using System.Reflection.Emit;
using System.Runtime.Serialization;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// Extensions to build attributes for the complex type builder.
@@ -92,7 +92,7 @@ public static void StructureDefinitionAttribute(
StructureDefinition structureDefinition)
{
Type attributeType = typeof(StructureDefinitionAttribute);
- StructureBaseDataType baseDataType = ComplexTypes.StructureDefinitionAttribute
+ StructureBaseDataType baseDataType = Emit.StructureDefinitionAttribute
.FromBaseType(
structureDefinition.BaseDataType);
// Reflection on local types: the parameterless constructor and named properties
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilder.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilder.cs
index b0bd012c78..ecfe18a476 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilder.cs
@@ -32,7 +32,7 @@
using System.Reflection;
using System.Reflection.Emit;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// Build an assembly with custom enum types and
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFactory.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilderFactory.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFactory.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilderFactory.cs
index 4ae7deea58..41e94bec98 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFactory.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilderFactory.cs
@@ -33,7 +33,7 @@
using System.Linq;
using System.Xml;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// Factory function for the default complex type builder
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFieldBuilder.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeFieldBuilder.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFieldBuilder.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeFieldBuilder.cs
index bfe0d56d2e..baceb4a91c 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFieldBuilder.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeFieldBuilder.cs
@@ -33,7 +33,7 @@
using System.Reflection.Emit;
using System.Xml;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// Builder for property fields.
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/ComplexTypesEmitExtensions.cs
similarity index 64%
rename from Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/ComplexTypesEmitExtensions.cs
index de004335ac..674cc89bb2 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/ComplexTypesEmitExtensions.cs
@@ -27,21 +27,31 @@
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
-namespace Opc.Ua.Client.ComplexTypes
+using Opc.Ua.Client;
+using Opc.Ua.Client.ComplexTypes;
+using Opc.Ua.ComplexTypes;
+
+namespace Opc.Ua.ComplexTypes.Emit
{
///
- /// Adds static methods to create a complex type system to load
- /// custom types using reflection emit (legacy).
+ /// Static methods that produce a
+ /// backed by the Reflection.Emit-based
+ /// . Hosts that need runtime
+ /// concrete .NET classes for custom DataTypes use these instead of
+ /// the default (AOT-friendly)
+ /// constructors, which use .
///
- public static class ComplexTypesExtensions
+ public static class ComplexTypesEmitExtensions
{
extension(ComplexTypeSystem)
{
///
- /// Initializes the type system with a session to load the
- /// custom types using reflection emit.
+ /// Initializes a bound to
+ /// that materialises custom
+ /// DataTypes as runtime .NET classes via
+ /// .
///
- public static ComplexTypeSystem Create(
+ public static ComplexTypeSystem CreateWithReflectionEmit(
ISession session,
ITelemetryContext telemetry)
{
@@ -52,10 +62,12 @@ public static ComplexTypeSystem Create(
}
///
- /// Initializes the type system with a complex type resolver
- /// to load the custom types using reflection emit.
+ /// Initializes a bound to a
+ /// caller-supplied that
+ /// materialises custom DataTypes as runtime .NET classes
+ /// via .
///
- public static ComplexTypeSystem Create(
+ public static ComplexTypeSystem CreateWithReflectionEmit(
IComplexTypeResolver complexTypeResolver,
ITelemetryContext telemetry)
{
@@ -64,7 +76,6 @@ public static ComplexTypeSystem Create(
new ComplexTypeBuilderFactory(),
telemetry);
}
-
}
}
}
diff --git a/Libraries/Opc.Ua.ComplexTypes.Emit/Hosting/OpcUaComplexTypesEmitBuilderExtensions.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Hosting/OpcUaComplexTypesEmitBuilderExtensions.cs
new file mode 100644
index 0000000000..14cac6adf6
--- /dev/null
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Hosting/OpcUaComplexTypesEmitBuilderExtensions.cs
@@ -0,0 +1,80 @@
+/* ========================================================================
+ * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
+ *
+ * OPC Foundation MIT License 1.00
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The complete license agreement can be found here:
+ * http://opcfoundation.org/License/MIT/1.00/
+ * ======================================================================*/
+
+using System;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Opc.Ua;
+using Opc.Ua.Client.ComplexTypes;
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.ComplexTypes.Emit;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Opt-in extensions provided by
+ /// Opc.Ua.ComplexTypes.Emit: swap the default AOT-friendly
+ /// registration for one
+ /// that materialises custom DataTypes as runtime .NET classes via
+ /// .
+ ///
+ public static class OpcUaComplexTypesEmitBuilderExtensions
+ {
+ ///
+ /// Registers a that
+ /// produces instances backed by
+ /// the Reflection.Emit-based
+ /// . Replaces the
+ /// AOT-friendly default registration installed by
+ /// AddComplexTypes().
+ ///
+ ///
+ /// Calling this method also runs AddComplexTypes() so
+ /// callers do not need to chain both. Idempotent — calling
+ /// twice does not stack registrations.
+ ///
+ /// The OPC UA builder.
+ /// The same instance.
+ /// is null.
+ public static IOpcUaBuilder AddComplexTypesWithReflectionEmit(
+ this IOpcUaBuilder builder)
+ {
+ if (builder is null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ OpcUaComplexTypesBuilderExtensions.AddComplexTypes(builder);
+ builder.Services.Replace(
+ ServiceDescriptor.Singleton(
+ static sp => new ComplexTypeSystemFactory(
+ sp.GetRequiredService(),
+ static () => new ComplexTypeBuilderFactory())));
+ return builder;
+ }
+ }
+}
diff --git a/Libraries/Opc.Ua.ComplexTypes.Emit/Opc.Ua.ComplexTypes.Emit.csproj b/Libraries/Opc.Ua.ComplexTypes.Emit/Opc.Ua.ComplexTypes.Emit.csproj
new file mode 100644
index 0000000000..21724ae42a
--- /dev/null
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Opc.Ua.ComplexTypes.Emit.csproj
@@ -0,0 +1,35 @@
+
+
+ $(AssemblyPrefix).ComplexTypes.Emit
+ $(LibxTargetFrameworks)
+ $(PackagePrefix).Opc.Ua.ComplexTypes.Emit
+ Opc.Ua.ComplexTypes.Emit
+ OPC UA Complex Types Reflection.Emit Class Library
+ true
+ true
+ enable
+
+
+
+
+
+
+
+ $(PackageId).Debug
+
+
+
+
+
+
+
+
+
diff --git a/Libraries/Opc.Ua.ComplexTypes.Emit/Properties/AssemblyInfo.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..2b9848014c
--- /dev/null
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Properties/AssemblyInfo.cs
@@ -0,0 +1,32 @@
+/* ========================================================================
+ * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
+ *
+ * OPC Foundation MIT License 1.00
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The complete license agreement can be found here:
+ * http://opcfoundation.org/License/MIT/1.00/
+ * ======================================================================*/
+
+using System;
+
+[assembly: CLSCompliant(false)]
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/BaseComplexType.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/BaseComplexType.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/BaseComplexType.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/BaseComplexType.cs
index fff77dfdfe..099e20a1c9 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/BaseComplexType.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/BaseComplexType.cs
@@ -36,7 +36,7 @@
using System.Text;
using System.Xml;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// The base class for all complex types.
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/ComplexTypePropertyInfo.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/ComplexTypePropertyInfo.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/ComplexTypePropertyInfo.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/ComplexTypePropertyInfo.cs
index d6a0c78302..17b9cae181 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/ComplexTypePropertyInfo.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/ComplexTypePropertyInfo.cs
@@ -32,7 +32,7 @@
using System.Reflection;
using System.Runtime.Serialization;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// Complex type property info.
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/IComplexTypeProperties.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/IComplexTypeProperties.cs
similarity index 98%
rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/IComplexTypeProperties.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/IComplexTypeProperties.cs
index 9c2a9f1312..4b576dd666 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/IComplexTypeProperties.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/IComplexTypeProperties.cs
@@ -30,7 +30,7 @@
using System;
using System.Collections.Generic;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// Interface to access properties of a complex type.
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/OptionalFieldsComplexType.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/OptionalFieldsComplexType.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/OptionalFieldsComplexType.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/OptionalFieldsComplexType.cs
index 4da3eca528..0c17561b81 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/OptionalFieldsComplexType.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/OptionalFieldsComplexType.cs
@@ -31,7 +31,7 @@
using System.Collections.Generic;
using System.Text;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// A complex type with optional fields.
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureDefinitionAttribute.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureDefinitionAttribute.cs
similarity index 98%
rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureDefinitionAttribute.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureDefinitionAttribute.cs
index de220e6b74..25862210cd 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureDefinitionAttribute.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureDefinitionAttribute.cs
@@ -29,7 +29,7 @@
using System;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// The known base complex types.
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureFieldAttribute.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureFieldAttribute.cs
similarity index 98%
rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureFieldAttribute.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureFieldAttribute.cs
index 4a98e87bcb..ed5e168544 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureFieldAttribute.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureFieldAttribute.cs
@@ -29,7 +29,7 @@
using System;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// Attribute for a base complex type field definition.
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureTypeAttribute.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureTypeAttribute.cs
similarity index 98%
rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureTypeAttribute.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureTypeAttribute.cs
index 0b7c9f424d..1540421a6f 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureTypeAttribute.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureTypeAttribute.cs
@@ -29,7 +29,7 @@
using System;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// Attribute for type ids of a structure definition.
diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/UnionComplexType.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/UnionComplexType.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/UnionComplexType.cs
rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/UnionComplexType.cs
index a59b65067d..20d74b2ad0 100644
--- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/UnionComplexType.cs
+++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/UnionComplexType.cs
@@ -31,7 +31,7 @@
using System.Collections.Generic;
using System.Text;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes.Emit
{
///
/// Implements a union complex type.
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeBuilder.cs b/Libraries/Opc.Ua.ComplexTypes/Builders/DefaultComplexTypeBuilder.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeBuilder.cs
rename to Libraries/Opc.Ua.ComplexTypes/Builders/DefaultComplexTypeBuilder.cs
index fd2ca30b32..34a227002b 100644
--- a/Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeBuilder.cs
+++ b/Libraries/Opc.Ua.ComplexTypes/Builders/DefaultComplexTypeBuilder.cs
@@ -29,7 +29,7 @@
using System.Xml;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes
{
///
/// Default complex type builder
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeFactory.cs b/Libraries/Opc.Ua.ComplexTypes/Builders/DefaultComplexTypeFactory.cs
similarity index 98%
rename from Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeFactory.cs
rename to Libraries/Opc.Ua.ComplexTypes/Builders/DefaultComplexTypeFactory.cs
index 923e6a2766..aa2140e3c5 100644
--- a/Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeFactory.cs
+++ b/Libraries/Opc.Ua.ComplexTypes/Builders/DefaultComplexTypeFactory.cs
@@ -30,7 +30,7 @@
using System.Collections.Generic;
using System.Xml;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes
{
///
/// Default complex type factory
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeFieldBuilder.cs b/Libraries/Opc.Ua.ComplexTypes/Builders/DefaultComplexTypeFieldBuilder.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeFieldBuilder.cs
rename to Libraries/Opc.Ua.ComplexTypes/Builders/DefaultComplexTypeFieldBuilder.cs
index 3a33081835..53e57c06c6 100644
--- a/Libraries/Opc.Ua.Client/ComplexTypes/DefaultComplexTypeFieldBuilder.cs
+++ b/Libraries/Opc.Ua.ComplexTypes/Builders/DefaultComplexTypeFieldBuilder.cs
@@ -31,7 +31,7 @@
using System.Collections.Generic;
using System.Xml;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes
{
///
/// Complex type field builder
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/IComplexTypeFactory.cs b/Libraries/Opc.Ua.ComplexTypes/Builders/IComplexTypeFactory.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client/ComplexTypes/IComplexTypeFactory.cs
rename to Libraries/Opc.Ua.ComplexTypes/Builders/IComplexTypeFactory.cs
index 79dea87fda..587bf58a4b 100644
--- a/Libraries/Opc.Ua.Client/ComplexTypes/IComplexTypeFactory.cs
+++ b/Libraries/Opc.Ua.ComplexTypes/Builders/IComplexTypeFactory.cs
@@ -29,7 +29,7 @@
using System.Collections.Generic;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes
{
///
/// Factory class for the complex type builder.
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/IComplexTypeResolver.cs b/Libraries/Opc.Ua.ComplexTypes/Builders/IComplexTypeResolver.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client/ComplexTypes/IComplexTypeResolver.cs
rename to Libraries/Opc.Ua.ComplexTypes/Builders/IComplexTypeResolver.cs
index 07cc65de77..a1ce12d12a 100644
--- a/Libraries/Opc.Ua.Client/ComplexTypes/IComplexTypeResolver.cs
+++ b/Libraries/Opc.Ua.ComplexTypes/Builders/IComplexTypeResolver.cs
@@ -31,7 +31,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes
{
///
/// Interface for the complex type builder.
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/DataTypeException.cs b/Libraries/Opc.Ua.ComplexTypes/Exceptions/DataTypeException.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client/ComplexTypes/DataTypeException.cs
rename to Libraries/Opc.Ua.ComplexTypes/Exceptions/DataTypeException.cs
index 9f0b0608d1..393d548341 100644
--- a/Libraries/Opc.Ua.Client/ComplexTypes/DataTypeException.cs
+++ b/Libraries/Opc.Ua.ComplexTypes/Exceptions/DataTypeException.cs
@@ -29,7 +29,7 @@
using System;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes
{
///
/// Exception is thrown if the data type is not found.
diff --git a/Libraries/Opc.Ua.ComplexTypes/Opc.Ua.ComplexTypes.csproj b/Libraries/Opc.Ua.ComplexTypes/Opc.Ua.ComplexTypes.csproj
new file mode 100644
index 0000000000..15a5224d1d
--- /dev/null
+++ b/Libraries/Opc.Ua.ComplexTypes/Opc.Ua.ComplexTypes.csproj
@@ -0,0 +1,27 @@
+
+
+ $(AssemblyPrefix).ComplexTypes
+ $(LibxTargetFrameworks)
+ $(PackagePrefix).Opc.Ua.ComplexTypes
+ Opc.Ua.ComplexTypes
+ OPC UA Complex Types Common Class Library
+ true
+ true
+ true
+ enable
+
+
+
+
+
+
+
+
+
+ $(PackageId).Debug
+
+
+
+
+
+
diff --git a/Libraries/Opc.Ua.ComplexTypes/Properties/AssemblyInfo.cs b/Libraries/Opc.Ua.ComplexTypes/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..2b9848014c
--- /dev/null
+++ b/Libraries/Opc.Ua.ComplexTypes/Properties/AssemblyInfo.cs
@@ -0,0 +1,32 @@
+/* ========================================================================
+ * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
+ *
+ * OPC Foundation MIT License 1.00
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The complete license agreement can be found here:
+ * http://opcfoundation.org/License/MIT/1.00/
+ * ======================================================================*/
+
+using System;
+
+[assembly: CLSCompliant(false)]
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/DataDictionary.cs b/Libraries/Opc.Ua.ComplexTypes/Schema/DataDictionary.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client/ComplexTypes/DataDictionary.cs
rename to Libraries/Opc.Ua.ComplexTypes/Schema/DataDictionary.cs
index 7d0b92d0a9..545d7f5573 100644
--- a/Libraries/Opc.Ua.Client/ComplexTypes/DataDictionary.cs
+++ b/Libraries/Opc.Ua.ComplexTypes/Schema/DataDictionary.cs
@@ -34,7 +34,7 @@
using Microsoft.Extensions.Logging;
using Opc.Ua.Schema;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes
{
///
/// A class that holds the configuration for a UA service.
diff --git a/Libraries/Opc.Ua.Client/ComplexTypes/DataTypeDefinitionExtension.cs b/Libraries/Opc.Ua.ComplexTypes/Schema/DataTypeDefinitionExtension.cs
similarity index 99%
rename from Libraries/Opc.Ua.Client/ComplexTypes/DataTypeDefinitionExtension.cs
rename to Libraries/Opc.Ua.ComplexTypes/Schema/DataTypeDefinitionExtension.cs
index a4ded4c219..834ef5a7e3 100644
--- a/Libraries/Opc.Ua.Client/ComplexTypes/DataTypeDefinitionExtension.cs
+++ b/Libraries/Opc.Ua.ComplexTypes/Schema/DataTypeDefinitionExtension.cs
@@ -31,7 +31,7 @@
using System.Linq;
using System.Xml;
-namespace Opc.Ua.Client.ComplexTypes
+namespace Opc.Ua.ComplexTypes
{
///
/// Extensions to convert binary schema type definitions to DataTypeDefinitions.
diff --git a/Libraries/Opc.Ua.Server.ComplexTypes/Opc.Ua.Server.ComplexTypes.csproj b/Libraries/Opc.Ua.Server.ComplexTypes/Opc.Ua.Server.ComplexTypes.csproj
new file mode 100644
index 0000000000..9bb8fb98d0
--- /dev/null
+++ b/Libraries/Opc.Ua.Server.ComplexTypes/Opc.Ua.Server.ComplexTypes.csproj
@@ -0,0 +1,25 @@
+
+
+ $(AssemblyPrefix).Server.ComplexTypes
+ $(LibxTargetFrameworks)
+ $(PackagePrefix).Opc.Ua.Server.ComplexTypes
+ Opc.Ua.Server.ComplexTypes
+ OPC UA Complex Types Server Class Library
+ true
+ true
+ true
+ enable
+
+
+
+
+
+ $(PackageId).Debug
+
+
+
+
+
+
+
+
diff --git a/Libraries/Opc.Ua.Server.ComplexTypes/OpcUaComplexTypesServerBuilderExtensions.cs b/Libraries/Opc.Ua.Server.ComplexTypes/OpcUaComplexTypesServerBuilderExtensions.cs
new file mode 100644
index 0000000000..5cadac92e6
--- /dev/null
+++ b/Libraries/Opc.Ua.Server.ComplexTypes/OpcUaComplexTypesServerBuilderExtensions.cs
@@ -0,0 +1,70 @@
+/* ========================================================================
+ * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
+ *
+ * OPC Foundation MIT License 1.00
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The complete license agreement can be found here:
+ * http://opcfoundation.org/License/MIT/1.00/
+ * ======================================================================*/
+
+using System;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Opc.Ua;
+using Opc.Ua.Server;
+using Opc.Ua.Server.ComplexTypes;
+using Opc.Ua.Server.Hosting;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// extensions provided by
+ /// Opc.Ua.Server.ComplexTypes: register a
+ /// so that hosted
+ /// servers can resolve typed complex-type loaders.
+ ///
+ public static class OpcUaComplexTypesServerBuilderExtensions
+ {
+ ///
+ /// Registers the singleton
+ /// service so
+ /// consumers can resolve it and produce a
+ /// per
+ /// .
+ ///
+ ///
+ /// Idempotent: repeated calls do not add a second descriptor.
+ ///
+ /// The OPC UA server builder.
+ /// The same instance.
+ /// is null.
+ public static IOpcUaServerBuilder AddComplexTypes(this IOpcUaServerBuilder builder)
+ {
+ if (builder is null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ builder.Services.TryAddSingleton();
+ return builder;
+ }
+ }
+}
diff --git a/Libraries/Opc.Ua.Server.ComplexTypes/Properties/AssemblyInfo.cs b/Libraries/Opc.Ua.Server.ComplexTypes/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..2b9848014c
--- /dev/null
+++ b/Libraries/Opc.Ua.Server.ComplexTypes/Properties/AssemblyInfo.cs
@@ -0,0 +1,32 @@
+/* ========================================================================
+ * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
+ *
+ * OPC Foundation MIT License 1.00
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The complete license agreement can be found here:
+ * http://opcfoundation.org/License/MIT/1.00/
+ * ======================================================================*/
+
+using System;
+
+[assembly: CLSCompliant(false)]
diff --git a/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystem.cs b/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystem.cs
new file mode 100644
index 0000000000..69d7116160
--- /dev/null
+++ b/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystem.cs
@@ -0,0 +1,289 @@
+/* ========================================================================
+ * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
+ *
+ * OPC Foundation MIT License 1.00
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The complete license agreement can be found here:
+ * http://opcfoundation.org/License/MIT/1.00/
+ * ======================================================================*/
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Logging;
+using Opc.Ua.ComplexTypes;
+
+namespace Opc.Ua.Server.ComplexTypes
+{
+ ///
+ /// Registers custom Structure / Enumeration DataTypes on a hosted
+ /// server's so the server can encode
+ /// and decode instances of those types on the wire.
+ ///
+ ///
+ ///
+ /// Hosts typically feed this loader with definitions discovered while
+ /// parsing a NodeSet2 (the parsed
+ /// exposes
+ /// DataTypeDefinition for every custom DataType), or they
+ /// build the definitions programmatically.
+ ///
+ ///
+ /// The loader uses an injected to
+ /// materialise runtime .NET types — either the AOT-friendly
+ /// DefaultComplexTypeFactory from Opc.Ua.ComplexTypes
+ /// or the Reflection.Emit-based ComplexTypeBuilderFactory when
+ /// concrete .NET classes are required.
+ ///
+ ///
+ /// Field DataTypes that are not yet known to the encodeable factory
+ /// cause the owning structure to be deferred. Call
+ /// after registering a batch to retry deferred
+ /// structures (multiple passes are made automatically until no
+ /// further progress is possible).
+ ///
+ ///
+ public class ServerComplexTypeSystem
+ {
+ private const int MaxLoopCount = 100;
+
+ ///
+ /// Initializes the type system bound to a hosted server's address
+ /// space and a complex type factory.
+ ///
+ /// The hosted server.
+ /// The complex type builder factory used to
+ /// produce runtime types.
+ /// The host's telemetry context.
+ /// Any argument is null.
+ public ServerComplexTypeSystem(
+ IServerInternal server,
+ IComplexTypeFactory factory,
+ ITelemetryContext telemetry)
+ {
+ m_server = server ?? throw new ArgumentNullException(nameof(server));
+ m_factory = factory ?? throw new ArgumentNullException(nameof(factory));
+ if (telemetry is null)
+ {
+ throw new ArgumentNullException(nameof(telemetry));
+ }
+ m_logger = telemetry.CreateLogger();
+ }
+
+ ///
+ /// Registers an enumeration DataType on the server's encodeable
+ /// factory.
+ ///
+ /// The DataType NodeId in the server's
+ /// address space.
+ /// The DataType browse name.
+ /// The enumeration definition.
+ /// true when the runtime type was created and
+ /// registered.
+ /// Any argument is null.
+ public bool RegisterEnumeration(
+ NodeId dataTypeId,
+ QualifiedName browseName,
+ EnumDefinition enumDefinition)
+ {
+ if (dataTypeId.IsNull)
+ {
+ throw new ArgumentException("DataType NodeId must not be null.", nameof(dataTypeId));
+ }
+ if (browseName.IsNull)
+ {
+ throw new ArgumentException("Browse name must not be null.", nameof(browseName));
+ }
+ if (enumDefinition is null)
+ {
+ throw new ArgumentNullException(nameof(enumDefinition));
+ }
+
+ IComplexTypeBuilder builder = m_factory.Create(
+ GetNamespaceUri(dataTypeId.NamespaceIndex),
+ dataTypeId.NamespaceIndex);
+ IEnumeratedType? type = builder.AddEnumType(browseName, enumDefinition);
+ if (type is null)
+ {
+ return false;
+ }
+ m_server.Factory.Builder
+ .AddEnumeratedType(type)
+ .Commit();
+ return true;
+ }
+
+ ///
+ /// Registers a structured DataType on the server's encodeable
+ /// factory. If any field DataType is not yet resolvable, the
+ /// structure is queued and retried on the next
+ /// call.
+ ///
+ /// The DataType NodeId in the server's
+ /// address space.
+ /// The DataType browse name.
+ /// The structure definition.
+ /// true when the structure was built and
+ /// registered immediately; false when it was deferred.
+ /// Any argument is null.
+ public bool RegisterStructure(
+ NodeId dataTypeId,
+ QualifiedName browseName,
+ StructureDefinition structureDefinition)
+ {
+ if (dataTypeId.IsNull)
+ {
+ throw new ArgumentException("DataType NodeId must not be null.", nameof(dataTypeId));
+ }
+ if (browseName.IsNull)
+ {
+ throw new ArgumentException("Browse name must not be null.", nameof(browseName));
+ }
+ if (structureDefinition is null)
+ {
+ throw new ArgumentNullException(nameof(structureDefinition));
+ }
+
+ if (TryBuildStructure(dataTypeId, browseName, structureDefinition))
+ {
+ return true;
+ }
+
+ m_pending.Add((dataTypeId, browseName, structureDefinition));
+ return false;
+ }
+
+ ///
+ /// Retries any structured DataTypes that were deferred because
+ /// their field DataTypes were not yet resolvable. Multiple passes
+ /// are made until no further progress is possible.
+ ///
+ /// The number of pending structures still unresolved
+ /// after all passes (zero on full success).
+ public int Flush()
+ {
+ int previousCount;
+ int iteration = 0;
+ do
+ {
+ previousCount = m_pending.Count;
+ for (int i = m_pending.Count - 1; i >= 0; i--)
+ {
+ (NodeId nodeId, QualifiedName name, StructureDefinition def) = m_pending[i];
+ if (TryBuildStructure(nodeId, name, def))
+ {
+ m_pending.RemoveAt(i);
+ }
+ }
+ iteration++;
+ }
+ while (m_pending.Count > 0 &&
+ m_pending.Count < previousCount &&
+ iteration < MaxLoopCount);
+
+ if (m_pending.Count > 0)
+ {
+ m_logger.LogWarning(
+ "{Remaining} custom structure DataType(s) could not be resolved " +
+ "(unresolved field DataTypes).",
+ m_pending.Count);
+ }
+ return m_pending.Count;
+ }
+
+ private bool TryBuildStructure(
+ NodeId dataTypeId,
+ QualifiedName browseName,
+ StructureDefinition definition)
+ {
+ // Verify every field's DataType is already known. If not,
+ // signal the caller to defer.
+ foreach (StructureField field in definition.Fields)
+ {
+ if (ResolveFieldType(field) is null)
+ {
+ return false;
+ }
+ }
+
+ IComplexTypeBuilder builder = m_factory.Create(
+ GetNamespaceUri(dataTypeId.NamespaceIndex),
+ dataTypeId.NamespaceIndex);
+
+ IComplexTypeFieldBuilder fieldBuilder = builder.AddStructuredType(
+ browseName,
+ definition);
+ fieldBuilder.AddTypeIdAttribute(
+ NodeId.ToExpandedNodeId(dataTypeId, m_server.NamespaceUris),
+ ExpandedNodeId.Null,
+ ExpandedNodeId.Null);
+
+ bool allowSubTypes = definition.StructureType is
+ StructureType.StructureWithSubtypedValues or
+ StructureType.UnionWithSubtypedValues;
+ int order = 1;
+ foreach (StructureField field in definition.Fields)
+ {
+ IType? fieldType = ResolveFieldType(field);
+ if (fieldType is null)
+ {
+ // Race: lost a type between the pre-check and here. Defer.
+ return false;
+ }
+ fieldBuilder.AddField(field, fieldType, order, allowSubTypes);
+ order++;
+ }
+
+ IEncodeableType created = fieldBuilder.CreateType();
+ m_server.Factory.Builder
+ .AddEncodeableType(created)
+ .Commit();
+ return true;
+ }
+
+ private IType? ResolveFieldType(StructureField field)
+ {
+ NodeId dataTypeId = field.DataType;
+ if (dataTypeId.IsNull)
+ {
+ return null;
+ }
+ ExpandedNodeId expanded = NodeId.ToExpandedNodeId(
+ dataTypeId,
+ m_server.NamespaceUris);
+ if (m_server.Factory.TryGetType(expanded, out IType? type))
+ {
+ return type;
+ }
+ return null;
+ }
+
+ private string GetNamespaceUri(ushort namespaceIndex)
+ => m_server.NamespaceUris.GetString(namespaceIndex) ?? string.Empty;
+
+ private readonly IServerInternal m_server;
+ private readonly IComplexTypeFactory m_factory;
+ private readonly ILogger m_logger;
+ private readonly List<(NodeId NodeId, QualifiedName Name, StructureDefinition Definition)>
+ m_pending = [];
+ }
+}
diff --git a/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystemFactory.cs b/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystemFactory.cs
new file mode 100644
index 0000000000..05892cace8
--- /dev/null
+++ b/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystemFactory.cs
@@ -0,0 +1,128 @@
+/* ========================================================================
+ * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
+ *
+ * OPC Foundation MIT License 1.00
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The complete license agreement can be found here:
+ * http://opcfoundation.org/License/MIT/1.00/
+ * ======================================================================*/
+
+using System;
+using Opc.Ua.ComplexTypes;
+
+namespace Opc.Ua.Server.ComplexTypes
+{
+ ///
+ /// Dependency-injected factory that produces a fresh
+ /// bound to a caller-supplied
+ /// and the host's
+ /// .
+ ///
+ ///
+ /// Registered as a singleton by
+ /// IOpcUaServerBuilder.AddComplexTypes(). By default the
+ /// factory produces type loaders backed by the AOT-friendly
+ /// . Hosts that need
+ /// runtime concrete .NET classes for custom DataTypes register the
+ /// Reflection.Emit-based ComplexTypeBuilderFactory from
+ /// Opc.Ua.ComplexTypes.Emit via
+ /// AddComplexTypesWithReflectionEmit().
+ ///
+ public sealed class ServerComplexTypeSystemFactory
+ {
+ ///
+ /// Initializes a new instance backed by
+ /// .
+ ///
+ /// The shared telemetry context.
+ /// is null.
+ public ServerComplexTypeSystemFactory(ITelemetryContext telemetry)
+ : this(telemetry, static () => new DefaultComplexTypeFactory())
+ {
+ }
+
+ ///
+ /// Initializes a new instance backed by a caller-supplied
+ /// source. The
+ /// delegate is
+ /// invoked once per call
+ /// so each gets its own
+ /// builder factory.
+ ///
+ /// The shared telemetry context.
+ /// Delegate that
+ /// produces a fresh per
+ /// server instance.
+ /// Any argument is null.
+ public ServerComplexTypeSystemFactory(
+ ITelemetryContext telemetry,
+ Func complexTypeFactoryFactory)
+ {
+ m_telemetry = telemetry ?? throw new ArgumentNullException(nameof(telemetry));
+ m_complexTypeFactoryFactory = complexTypeFactoryFactory ??
+ throw new ArgumentNullException(nameof(complexTypeFactoryFactory));
+ }
+
+ ///
+ /// Creates a new bound to
+ /// and the host's
+ /// .
+ ///
+ /// The hosted server.
+ /// A fresh .
+ /// is null.
+ public ServerComplexTypeSystem Create(IServerInternal server)
+ {
+ return Create(server, m_complexTypeFactoryFactory());
+ }
+
+ ///
+ /// Creates a new bound to
+ /// using a caller-supplied
+ /// . This overload bypasses
+ /// the configured factory source and is useful when callers
+ /// already hold a built factory.
+ ///
+ /// The hosted server.
+ /// The complex type builder factory.
+ /// A fresh .
+ /// Any argument is null.
+ public ServerComplexTypeSystem Create(
+ IServerInternal server,
+ IComplexTypeFactory factory)
+ {
+ if (server is null)
+ {
+ throw new ArgumentNullException(nameof(server));
+ }
+ if (factory is null)
+ {
+ throw new ArgumentNullException(nameof(factory));
+ }
+ return new ServerComplexTypeSystem(server, factory, m_telemetry);
+ }
+
+ private readonly ITelemetryContext m_telemetry;
+ private readonly Func m_complexTypeFactoryFactory;
+ }
+}
diff --git a/OpcComplexTypeSystemOverhaul.md b/OpcComplexTypeSystemOverhaul.md
new file mode 100644
index 0000000000..97218b5ea3
--- /dev/null
+++ b/OpcComplexTypeSystemOverhaul.md
@@ -0,0 +1,324 @@
+# Plan: Split ComplexTypes into Common / Client / Server (revised against actual repo state)
+
+> **Status:** revised plan against `master @ f3928db0`. The original plan was
+> written against an earlier shape of the repo; this version reflects what is
+> actually on disk today and the user's chosen scope:
+>
+> - **Breaking change** — no `[TypeForwardedTo]` shims, no `Obsolete` wrappers.
+> - **Minimal working** server impl (not just a skeleton).
+> - Full build + ComplexTypes test runs are the acceptance gate.
+
+---
+
+## 1. Current state (as of f3928db0)
+
+The original plan assumed every `IComplexType*` interface plus
+`ComplexTypeSystem`, `NodeCacheResolver`, `DataDictionary`, and the exception
+types lived in `Libraries/Opc.Ua.Client.ComplexTypes/`. **They don't.** The
+current layout is:
+
+### `Libraries/Opc.Ua.Client/ComplexTypes/` (inside the main client project)
+- Interfaces — `IComplexTypeFactory.cs`, `IComplexTypeResolver.cs` (the file
+ also defines `IComplexTypeBuilder`/`IComplexTypeFieldBuilder`)
+- Default (AOT-friendly, non-Emit) implementations:
+ - `DefaultComplexTypeFactory.cs`
+ - `DefaultComplexTypeBuilder.cs`
+ - `DefaultComplexTypeFieldBuilder.cs`
+- Orchestration / client-only:
+ - `ComplexTypeSystem.cs` — main loader, depends on `ISession`
+ - `NodeCacheResolver.cs` — `IComplexTypeResolver` backed by a session's
+ `NodeCache`
+ - `DataDictionary.cs`, `DataTypeDefinitionExtension.cs`
+- Exceptions — `DataTypeException.cs` (declares both
+ `DataTypeNotFoundException` and `DataTypeNotSupportedException`)
+- Namespace: **`Opc.Ua.Client.ComplexTypes`** (yes — even though the files
+ live in `Opc.Ua.Client.csproj`).
+
+### `Libraries/Opc.Ua.Client.ComplexTypes/` (the legacy add-on package)
+- Reflection.Emit builders (NOT AOT-compatible):
+ - `TypeBuilder/ComplexTypeBuilderFactory.cs` — `IComplexTypeFactory`
+ - `TypeBuilder/ComplexTypeBuilder.cs` — `IComplexTypeBuilder`
+ - `TypeBuilder/ComplexTypeFieldBuilder.cs` — `IComplexTypeFieldBuilder`
+ - `TypeBuilder/AssemblyModule.cs`, `TypeBuilder/AttributeExtensions.cs`
+- Runtime types that Emit-generated classes derive from:
+ - `Types/BaseComplexType.cs`, `Types/OptionalFieldsComplexType.cs`,
+ `Types/UnionComplexType.cs`
+ - `Types/ComplexTypePropertyInfo.cs`, `Types/IComplexTypeProperties.cs`
+ - `Types/StructureDefinitionAttribute.cs`,
+ `Types/StructureFieldAttribute.cs`, `Types/StructureTypeAttribute.cs`
+- DI / convenience surface:
+ - `OpcUaComplexTypesBuilderExtensions.cs` — `IOpcUaBuilder.AddComplexTypes()`
+ - `ComplexTypeSystemFactory.cs` — DI-resolvable factory that builds a
+ `ComplexTypeSystem` per `ISession`
+ - `ComplexTypesExtensions.cs` — C#-extension members on
+ `ComplexTypeSystem` for `Create(session, telemetry)` /
+ `Create(IComplexTypeResolver, telemetry)`
+- `Properties/AssemblyInfo.cs` — `[assembly: CLSCompliant(false)]`
+- Namespace: **`Opc.Ua.Client.ComplexTypes`** (same namespace as the files
+ inside `Opc.Ua.Client.csproj` — fine because consumers see one namespace).
+
+### Tests
+- `Tests/Opc.Ua.Client.ComplexTypes.Tests/` — tests the Emit factory,
+ type-system loading via `MockResolver`, encoder behaviour, and the
+ `AddComplexTypes()` DI surface.
+- `Tests/Opc.Ua.Client.Tests/ComplexTypes/` — tests the **default**
+ (non-Emit) builder, plus `NodeCacheResolver`, `DataDictionary`,
+ `ComplexTypeSystem` end-to-end with `Quickstarts.Servers`.
+
+### Consumers using `Opc.Ua.Client.ComplexTypes` namespace today
+13 in-repo files (apps, tests, docs):
+`ConsoleReferenceClient/Program.cs`, `ConsoleReferenceClient/ClientSamples.cs`,
+all `Tests/Opc.Ua.Client.Tests/ComplexTypes/*.cs`,
+`Tests/Opc.Ua.Aot.Tests/ComplexTypeAotTests.cs`, plus
+`Docs/ComplexTypes.md` and the legacy plan doc.
+
+---
+
+## 2. Target layout
+
+```
+Libraries/
+ Opc.Ua.ComplexTypes/ ← NEW common library
+ Builders/
+ IComplexTypeFactory.cs
+ IComplexTypeResolver.cs (and IComplexTypeBuilder / IComplexTypeFieldBuilder)
+ DefaultComplexTypeFactory.cs
+ DefaultComplexTypeBuilder.cs
+ DefaultComplexTypeFieldBuilder.cs
+ ComplexTypeBuilderFactory.cs (Emit)
+ ComplexTypeBuilder.cs (Emit)
+ ComplexTypeFieldBuilder.cs (Emit)
+ AssemblyModule.cs
+ AttributeExtensions.cs
+ Types/
+ BaseComplexType.cs
+ OptionalFieldsComplexType.cs
+ UnionComplexType.cs
+ ComplexTypePropertyInfo.cs
+ IComplexTypeProperties.cs
+ StructureDefinitionAttribute.cs
+ StructureFieldAttribute.cs
+ StructureTypeAttribute.cs
+ Schema/
+ DataDictionary.cs
+ DataTypeDefinitionExtension.cs
+ Exceptions/
+ DataTypeException.cs (both exception classes)
+ Properties/AssemblyInfo.cs ([CLSCompliant(false)] moved here)
+ Opc.Ua.ComplexTypes.csproj
+
+ Opc.Ua.Client.ComplexTypes/ ← refactored
+ ComplexTypeSystem.cs (moved from Opc.Ua.Client)
+ NodeCacheResolver.cs (moved from Opc.Ua.Client)
+ ComplexTypeSystemFactory.cs (stays)
+ ComplexTypesExtensions.cs (stays — extension on ComplexTypeSystem)
+ OpcUaComplexTypesBuilderExtensions.cs (stays — AddComplexTypes())
+ Opc.Ua.Client.ComplexTypes.csproj
+
+ Opc.Ua.Server.ComplexTypes/ ← NEW
+ AddressSpaceComplexTypeResolver.cs (IComplexTypeResolver over IServerInternal/INodeManager)
+ ServerComplexTypeSystem.cs (server-side loader; analogous to client ComplexTypeSystem)
+ ServerComplexTypeSystemFactory.cs (DI factory bound to telemetry)
+ OpcUaComplexTypesServerBuilderExtensions.cs (IOpcUaServerBuilder.AddComplexTypes())
+ Opc.Ua.Server.ComplexTypes.csproj
+```
+
+### Namespaces (breaking)
+
+| Old (Opc.Ua.Client.ComplexTypes) | New |
+| --- | --- |
+| `IComplexTypeFactory`, `IComplexTypeBuilder`, `IComplexTypeFieldBuilder`, `IComplexTypeResolver` | **`Opc.Ua.ComplexTypes`** |
+| `DefaultComplexType*` | **`Opc.Ua.ComplexTypes`** |
+| `ComplexTypeBuilder*`, `ComplexTypeFieldBuilder`, `AssemblyModule`, `AttributeExtensions` | **`Opc.Ua.ComplexTypes`** |
+| `BaseComplexType`, `OptionalFieldsComplexType`, `UnionComplexType`, `ComplexTypePropertyInfo`, `IComplexTypeProperties` | **`Opc.Ua.ComplexTypes`** |
+| `StructureDefinitionAttribute`, `StructureFieldAttribute`, `StructureTypeIdAttribute`, `StructureBaseDataType` | **`Opc.Ua.ComplexTypes`** |
+| `DataDictionary`, `DataTypeDefinitionExtension` | **`Opc.Ua.ComplexTypes`** |
+| `DataTypeNotFoundException`, `DataTypeNotSupportedException` | **`Opc.Ua.ComplexTypes`** |
+| `ComplexTypeSystem`, `NodeCacheResolver` | **`Opc.Ua.Client.ComplexTypes`** (unchanged) |
+| `ComplexTypeSystemFactory`, `ComplexTypesExtensions` | **`Opc.Ua.Client.ComplexTypes`** (unchanged) |
+| `OpcUaComplexTypesBuilderExtensions` | **`Microsoft.Extensions.DependencyInjection`** (unchanged) |
+
+### Project dependency graph (new)
+
+```
+Opc.Ua.Core
+ ▲
+ ├── Opc.Ua.ComplexTypes (new)
+ │ ▲
+ │ ├── Opc.Ua.Client (references ComplexTypes for interfaces/types)
+ │ │ ▲
+ │ │ └── Opc.Ua.Client.ComplexTypes (references both)
+ │ │
+ │ └── Opc.Ua.Server (no change required unless server uses types directly;
+ │ the new Opc.Ua.Server.ComplexTypes pulls these in)
+ │
+ └── Opc.Ua.Server.ComplexTypes (new) ──► Opc.Ua.Server, Opc.Ua.ComplexTypes
+```
+
+Note: `Opc.Ua.Client` will now reference `Opc.Ua.ComplexTypes` because its
+`SessionConfiguration` / orchestration types use the interfaces. No
+`Opc.Ua.Server` change is strictly required by the split — the new
+`Opc.Ua.Server.ComplexTypes` is opt-in.
+
+---
+
+## 3. Implementation phases
+
+### Phase A — Common project scaffold
+1. Create `Libraries/Opc.Ua.ComplexTypes/Opc.Ua.ComplexTypes.csproj` with the
+ same conventions used by `Opc.Ua.Client.ComplexTypes.csproj`:
+ - `AssemblyName = $(AssemblyPrefix).ComplexTypes`
+ - `PackageId = $(PackagePrefix).Opc.Ua.ComplexTypes`
+ - `RootNamespace = Opc.Ua.ComplexTypes`
+ - `TargetFrameworks = $(LibxTargetFrameworks)`
+ - `IsPackable = true`, `GenerateDocumentationFile = true`,
+ `Nullable = enable`, AOT-compatible on net10
+ - `InternalsVisibleTo` Client/Server addon test projects + main client
+2. Single `ProjectReference` to `Stack/Opc.Ua.Core/Opc.Ua.Core.csproj`.
+3. `Properties/AssemblyInfo.cs` carries `[assembly: CLSCompliant(false)]`.
+4. Add the project to `UA.slnx` under `/Libraries/`.
+
+### Phase B — Move shared files into Common
+1. Physically move (not copy) files into `Libraries/Opc.Ua.ComplexTypes/`:
+ - From `Libraries/Opc.Ua.Client/ComplexTypes/`:
+ `IComplexTypeFactory.cs`, `IComplexTypeResolver.cs`,
+ `DefaultComplexTypeFactory.cs`, `DefaultComplexTypeBuilder.cs`,
+ `DefaultComplexTypeFieldBuilder.cs`, `DataDictionary.cs`,
+ `DataTypeDefinitionExtension.cs`, `DataTypeException.cs`
+ - From `Libraries/Opc.Ua.Client.ComplexTypes/`:
+ entire `TypeBuilder/` directory, entire `Types/` directory,
+ `Properties/AssemblyInfo.cs`
+2. Update namespace on every moved file:
+ `Opc.Ua.Client.ComplexTypes` → `Opc.Ua.ComplexTypes`.
+3. Inside the moved files, drop any explicit
+ `using Opc.Ua.Client.ComplexTypes;` (now self-referencing); add
+ `using Opc.Ua.ComplexTypes;` where consumers from outside the file
+ reference its sibling types.
+4. Keep `ComplexTypesModule` constant in `AssemblyModule.cs` as
+ `Opc.Ua.ComplexTypes.Module` — already matches the new namespace.
+
+### Phase C — Refactor `Opc.Ua.Client` (main library)
+1. Move `ComplexTypeSystem.cs` and `NodeCacheResolver.cs` out of
+ `Libraries/Opc.Ua.Client/ComplexTypes/` into
+ `Libraries/Opc.Ua.Client.ComplexTypes/`. The empty `ComplexTypes/`
+ directory is removed.
+2. `Opc.Ua.Client.csproj`: add
+ ``.
+ This is required because `SessionConfiguration` and other client
+ internals still reference `IComplexTypeFactory` etc. Drop the
+ `InternalsVisibleTo` for `Opc.Ua.Client.ComplexTypes.Tests` only if the
+ tests no longer touch client internals (verify; likely keep).
+3. Update `using` statements in any remaining `Opc.Ua.Client` source that
+ referenced the moved types.
+
+### Phase D — Refactor `Opc.Ua.Client.ComplexTypes`
+1. `Opc.Ua.Client.ComplexTypes.csproj`:
+ - Drop the moved files (Types/, TypeBuilder/, Properties/).
+ - Add ``.
+2. In the remaining files (`ComplexTypeSystem.cs`, `NodeCacheResolver.cs`,
+ `ComplexTypeSystemFactory.cs`, `ComplexTypesExtensions.cs`,
+ `OpcUaComplexTypesBuilderExtensions.cs`):
+ - Keep namespace `Opc.Ua.Client.ComplexTypes`.
+ - Add `using Opc.Ua.ComplexTypes;` where needed.
+ - `ComplexTypesExtensions` still extends `ComplexTypeSystem` — no
+ namespace move required.
+ - `OpcUaComplexTypesBuilderExtensions` keeps its
+ `Microsoft.Extensions.DependencyInjection` namespace.
+
+### Phase E — Create `Opc.Ua.Server.ComplexTypes` (minimal working impl)
+The goal is enough functionality that a hosted server can advertise complex
+custom DataTypes whose definitions live in the server's address space (e.g.
+loaded from a NodeSet2) and round-trip those types on the wire.
+
+1. `Libraries/Opc.Ua.Server.ComplexTypes/Opc.Ua.Server.ComplexTypes.csproj`:
+ - Same conventions as sibling projects;
+ `AssemblyName = $(AssemblyPrefix).Server.ComplexTypes`,
+ `PackageId = $(PackagePrefix).Opc.Ua.Server.ComplexTypes`,
+ `RootNamespace = Opc.Ua.Server.ComplexTypes`,
+ `TargetFrameworks = $(LibxTargetFrameworks)`.
+ - References `Opc.Ua.Core`, `Opc.Ua.Server`, `Opc.Ua.ComplexTypes`.
+2. **`AddressSpaceComplexTypeResolver`** — implements `IComplexTypeResolver`
+ against an `IServerInternal` (or, more narrowly, against
+ `NodeManagerTable` + `MessageContext`). It walks the DataType subtype tree
+ in the address space, materialises `DataTypeNode`s from local nodes, and
+ exposes the same `LoadDataTypesAsync` / `FindAsync` / `FindSuperTypeAsync`
+ surface the client resolver provides. Browse helpers required by the
+ resolver interface are short-circuited because the server knows its own
+ encoding ids directly.
+3. **`ServerComplexTypeSystem`** — analog of the client
+ `ComplexTypeSystem` that consumes an `IComplexTypeResolver` (typically the
+ `AddressSpaceComplexTypeResolver`) and an `IComplexTypeFactory` (default
+ non-Emit or the Emit factory) and produces runtime types registered with
+ `IServerInternal.MessageContext.Factory`. Reuses the same per-namespace
+ walk the client uses; the wiring is much simpler because the resolver is
+ in-process.
+4. **`ServerComplexTypeSystemFactory`** + **`OpcUaComplexTypesServerBuilderExtensions`**
+ — DI surface analogous to the client side:
+ `IOpcUaServerBuilder.AddComplexTypes()` registers the factory as a
+ singleton against the configured server host so server code resolves a
+ `ServerComplexTypeSystem` bound to the live address space and the host's
+ `ITelemetryContext`.
+
+### Phase F — Update solution + tests
+1. `UA.slnx` — add the two new projects under `/Libraries/`.
+2. `Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj`:
+ - Add ProjectReference to `Opc.Ua.ComplexTypes` (transitive, but explicit
+ is clearer for the tests).
+3. `Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj`: add ProjectReference
+ to `Opc.Ua.ComplexTypes` (the `ComplexTypes/` test sub-folder uses these).
+4. Update `using` statements across all 13 consumer files:
+ - `using Opc.Ua.Client.ComplexTypes;` may need to become **both**
+ `using Opc.Ua.Client.ComplexTypes; using Opc.Ua.ComplexTypes;`
+ depending on which symbols the file touches. Triage per file.
+5. **New tests** (minimum):
+ - One smoke test in `Tests/Opc.Ua.Client.ComplexTypes.Tests` or a new
+ `Tests/Opc.Ua.ComplexTypes.Tests` exercising the moved
+ `ComplexTypeBuilderFactory` and `DefaultComplexTypeFactory` directly —
+ proving the Common surface compiles and runs without referencing
+ `Opc.Ua.Client.*`.
+ - One server smoke test (new `Tests/Opc.Ua.Server.ComplexTypes.Tests`,
+ or hosted inside `Tests/Opc.Ua.Server.Tests`) that boots a server with a
+ small custom DataType in a NodeSet2 and confirms
+ `ServerComplexTypeSystem.LoadAsync` registers the type on the
+ `MessageContext.Factory`.
+
+### Phase G — Documentation
+1. `Docs/ComplexTypes.md` — update to mention the three-package structure
+ and the new `Opc.Ua.ComplexTypes` namespace. Show migration snippets.
+2. `Docs/MigrationGuide.md` — append a "1.5 → 1.6" section calling out the
+ namespace move.
+3. NuGet README for the new packages (optional for the first PR — the
+ `Docs/NugetREADME.md` is shared today; leave as-is for v1).
+
+### Phase H — Verification gate (acceptance)
+1. `dotnet build UA.slnx` clean (TreatWarningsAsErrors is on).
+2. Run:
+ - `dotnet test Tests/Opc.Ua.Client.ComplexTypes.Tests/...`
+ - `dotnet test Tests/Opc.Ua.Client.Tests/... --filter "FullyQualifiedName~ComplexTypes"`
+ - `dotnet test Tests/Opc.Ua.Aot.Tests/... --filter "FullyQualifiedName~ComplexType"`
+ - Plus the new server smoke test if created.
+
+---
+
+## 4. Risk register (revised)
+
+| Risk | Mitigation |
+| --- | --- |
+| Hidden compile-time fan-out from the breaking namespace change (the 13 consumer files plus indirect ones). | Treat first build pass as a discovery tool; iterate using-statements until clean. |
+| `Opc.Ua.Client` now transitively depending on `Opc.Ua.ComplexTypes` increases its closure. | Acceptable — the new lib has only `Opc.Ua.Core` as a dependency. |
+| `InternalsVisibleTo` chain shifts (e.g. tests that poked at internal members of `Opc.Ua.Client.ComplexTypes` now need access to internals of `Opc.Ua.ComplexTypes`). | The new csproj declares `InternalsVisibleTo` for all three test assemblies; verify each `internal` usage. |
+| Server impl mis-models address-space traversal (the server's NodeId scheme for DataType encodings differs from a remote client's). | Start the server resolver by reusing the same browse semantics the client uses against the in-proc node managers; if that produces correct binary/xml encoding ids, ship it. |
+| Source generation (`Opc.Ua.SourceGeneration`) emits code referencing the old namespace. | Search the generation outputs at first build; either retarget the generator or add `using` aliases at consumption sites. |
+
+---
+
+## 5. Out of scope for this overhaul
+
+- Type forwarders / obsolete shims (user opted for clean break).
+- New server features beyond loading complex types into the encodeable
+ factory (no dictionary generation, no dynamic type creation API).
+- Re-organising tests beyond what the move strictly requires (e.g. a
+ dedicated `Opc.Ua.ComplexTypes.Tests` is optional; tests for the moved
+ types can live in the client test assembly for now).
+- NuGet metadata polish beyond what the new csproj inherits from
+ `common.props`.
diff --git a/OpcComplexTypesAotEmitSplit.md b/OpcComplexTypesAotEmitSplit.md
new file mode 100644
index 0000000000..dddd58a401
--- /dev/null
+++ b/OpcComplexTypesAotEmitSplit.md
@@ -0,0 +1,293 @@
+# Plan: Split `Opc.Ua.ComplexTypes` into AOT-Friendly Core + Opt-In Reflection.Emit Package
+
+> **Goal:** Keep the AOT (no-Emit) and Reflection.Emit paths as separate
+> NuGet packages that can be referenced and used as needed. After this
+> split, an AOT-published application can depend on the AOT-friendly
+> core alone and never load any Emit code; an application that wants
+> runtime concrete .NET classes for custom DataTypes opts into the
+> Emit package on top.
+>
+> Follow-on to `OpcComplexTypeSystemOverhaul.md`. Same conventions:
+> breaking change (no `[TypeForwardedTo]` shims), full build + tests
+> are the acceptance gate.
+
+---
+
+## 1. Current state (post-overhaul)
+
+The `Opc.Ua.ComplexTypes` library produced by the previous refactor
+mixes two categories of code:
+
+### AOT-friendly (no `Reflection.Emit`, trim-safe)
+| Folder | File(s) | Why it's AOT-safe |
+| --- | --- | --- |
+| `Builders/` | `IComplexTypeFactory.cs`, `IComplexTypeResolver.cs` | Pure interface surface |
+| `Builders/` | `DefaultComplexTypeFactory.cs`, `DefaultComplexTypeBuilder.cs`, `DefaultComplexTypeFieldBuilder.cs` | Use `Encoders.Enumeration` / `Encoders.OptionSet` from `Opc.Ua.Core`; no Emit, no `MakeArrayType`, no `Activator` on dynamic types |
+| `Schema/` | `DataDictionary.cs`, `DataTypeDefinitionExtension.cs` | XML schema parsing only |
+| `Exceptions/` | `DataTypeException.cs` | Plain exception classes |
+
+### NOT AOT-friendly (uses `Reflection.Emit` or runtime reflection on dynamic types)
+| Folder | File(s) | Why it isn't AOT-safe |
+| --- | --- | --- |
+| `Builders/` | `AssemblyModule.cs` | `AssemblyBuilder.DefineDynamicAssembly`, suppressed with `IL3050` |
+| `Builders/` | `ComplexTypeBuilder.cs`, `ComplexTypeBuilderFactory.cs`, `ComplexTypeFieldBuilder.cs` | `TypeBuilder.DefineType` / `DefineProperty` / IL emission |
+| `Builders/` | `AttributeExtensions.cs` | `CustomAttributeBuilder`, requires unreferenced code |
+| `Types/` | `BaseComplexType.cs`, `OptionalFieldsComplexType.cs`, `UnionComplexType.cs` | Runtime reflection on dynamically-generated derived classes (`Activator.CreateInstance`, `MemberwiseClone`, property walks) |
+| `Types/` | `ComplexTypePropertyInfo.cs`, `IComplexTypeProperties.cs` | Reflection on `PropertyInfo` of emitted classes |
+| `Types/` | `StructureDefinitionAttribute.cs`, `StructureFieldAttribute.cs`, `StructureTypeAttribute.cs` | Attribute classes ARE AOT-safe to declare, but their only consumer is `BaseComplexType` / Emit — keeping them with their consumers reduces fan-out |
+
+### Confirmed by grep
+- The source generator (`Tools/Opc.Ua.SourceGeneration*`) does **not** reference `BaseComplexType`, the union/optional types, or the attribute classes — it emits standalone classes implementing `IEncodeable` directly.
+- `Tests/Opc.Ua.Aot.Tests/ComplexTypeAotTests.cs` exercises the AOT path via `DefaultComplexTypeFactory` only.
+- The attribute classes are referenced only from `BaseComplexType` reflection paths, the Emit builders, and `MockResolverTests.cs` (which tests the Emit path).
+
+---
+
+## 2. Target layout
+
+```
+Libraries/
+ Opc.Ua.ComplexTypes/ ← stays, AOT-friendly only
+ Builders/
+ IComplexTypeFactory.cs (interfaces only)
+ IComplexTypeResolver.cs
+ DefaultComplexTypeFactory.cs
+ DefaultComplexTypeBuilder.cs
+ DefaultComplexTypeFieldBuilder.cs
+ Schema/
+ DataDictionary.cs
+ DataTypeDefinitionExtension.cs
+ Exceptions/
+ DataTypeException.cs
+ Properties/AssemblyInfo.cs
+ Opc.Ua.ComplexTypes.csproj
+ └── ProjectReference: Opc.Ua.Core
+ └── IsAotCompatible = true (already set)
+
+ Opc.Ua.ComplexTypes.Emit/ ← NEW opt-in package
+ Builders/
+ AssemblyModule.cs
+ ComplexTypeBuilder.cs
+ ComplexTypeBuilderFactory.cs
+ ComplexTypeFieldBuilder.cs
+ AttributeExtensions.cs
+ Types/
+ BaseComplexType.cs
+ OptionalFieldsComplexType.cs
+ UnionComplexType.cs
+ ComplexTypePropertyInfo.cs
+ IComplexTypeProperties.cs
+ StructureDefinitionAttribute.cs
+ StructureFieldAttribute.cs
+ StructureTypeAttribute.cs
+ Properties/AssemblyInfo.cs
+ Opc.Ua.ComplexTypes.Emit.csproj
+ └── ProjectReference: Opc.Ua.Core, Opc.Ua.ComplexTypes
+ └── IsAotCompatible = false (intentional — this package is the escape hatch)
+```
+
+### Namespaces (breaking)
+| Type | Old | New |
+| --- | --- | --- |
+| `BaseComplexType`, `OptionalFieldsComplexType`, `UnionComplexType` | `Opc.Ua.ComplexTypes` | **`Opc.Ua.ComplexTypes.Emit`** |
+| `ComplexTypePropertyInfo`, `IComplexTypeProperties` | `Opc.Ua.ComplexTypes` | **`Opc.Ua.ComplexTypes.Emit`** |
+| `StructureDefinitionAttribute`, `StructureFieldAttribute`, `StructureTypeIdAttribute`, `StructureBaseDataType` | `Opc.Ua.ComplexTypes` | **`Opc.Ua.ComplexTypes.Emit`** |
+| `ComplexTypeBuilder`, `ComplexTypeBuilderFactory`, `ComplexTypeFieldBuilder`, `AssemblyModule`, `AttributeExtensions` | `Opc.Ua.ComplexTypes` | **`Opc.Ua.ComplexTypes.Emit`** |
+| Everything in the AOT list above | `Opc.Ua.ComplexTypes` | **`Opc.Ua.ComplexTypes`** (unchanged) |
+
+NuGet package id: `OPCFoundation.NetStandard.Opc.Ua.ComplexTypes.Emit`.
+Assembly name: `Opc.Ua.ComplexTypes.Emit`.
+
+### Project dependency graph (new)
+
+```
+Opc.Ua.Core
+ ▲
+ ├── Opc.Ua.ComplexTypes (AOT-friendly)
+ │ ▲
+ │ ├── Opc.Ua.ComplexTypes.Emit (NEW, opt-in, NOT AOT)
+ │ │ ▲
+ │ │ └── (referenced by apps that need runtime concrete classes)
+ │ │
+ │ ├── Opc.Ua.Client.ComplexTypes
+ │ │ └── DI factory defaults to DefaultComplexTypeFactory
+ │ │
+ │ └── Opc.Ua.Server.ComplexTypes
+ │ └── DI factory defaults to DefaultComplexTypeFactory
+```
+
+---
+
+## 3. DI surface changes
+
+### `Opc.Ua.Client.ComplexTypes`
+
+| Before | After |
+| --- | --- |
+| `ComplexTypeSystemFactory.Create(session)` → returns `ComplexTypeSystem` bound to `ComplexTypeBuilderFactory` (Emit). | `ComplexTypeSystemFactory.Create(session)` → returns `ComplexTypeSystem` bound to **`DefaultComplexTypeFactory`** (AOT-friendly). |
+| `OpcUaComplexTypesBuilderExtensions.AddComplexTypes()` registers the singleton factory (which uses Emit). | `AddComplexTypes()` keeps the same name and signature but the registered factory uses `DefaultComplexTypeFactory`. |
+| `ComplexTypesExtensions.Create(ISession, ITelemetryContext)` → returns `ComplexTypeSystem` bound to `ComplexTypeBuilderFactory`. | Existing extension method **moves to** `Opc.Ua.ComplexTypes.Emit` (since it constructs an Emit factory). |
+
+### New DI surface in `Opc.Ua.ComplexTypes.Emit`
+
+New static class `OpcUaComplexTypesEmitBuilderExtensions` in the
+`Microsoft.Extensions.DependencyInjection` namespace:
+
+```csharp
+// Replace the default (AOT-friendly) ComplexTypeSystemFactory with one
+// that builds Emit-based ComplexTypeSystems. Apps that need runtime
+// concrete .NET classes for custom DataTypes call this instead of
+// AddComplexTypes().
+public static IOpcUaBuilder AddComplexTypesWithReflectionEmit(this IOpcUaBuilder builder);
+
+// Server-side equivalent.
+public static IOpcUaServerBuilder AddComplexTypesWithReflectionEmit(this IOpcUaServerBuilder builder);
+```
+
+Plus the migrated `ComplexTypesExtensions` (extension on `ComplexTypeSystem`):
+
+```csharp
+namespace Opc.Ua.Client.ComplexTypes
+{
+ public static class ComplexTypesEmitExtensions
+ {
+ extension(ComplexTypeSystem)
+ {
+ public static ComplexTypeSystem CreateWithReflectionEmit(
+ ISession session, ITelemetryContext telemetry);
+
+ public static ComplexTypeSystem CreateWithReflectionEmit(
+ IComplexTypeResolver resolver, ITelemetryContext telemetry);
+ }
+ }
+}
+```
+
+(Lives in the Emit package; namespace stays `Opc.Ua.Client.ComplexTypes`
+so the extension still attaches to `ComplexTypeSystem` — no consumer
+needs a second `using` directive.)
+
+### `Opc.Ua.Server.ComplexTypes`
+
+`ServerComplexTypeSystemFactory.Create(IServerInternal)` already
+defaults to `DefaultComplexTypeFactory`. The two-arg overload
+`Create(IServerInternal, IComplexTypeFactory)` continues to accept a
+caller-supplied Emit factory. The new
+`AddComplexTypesWithReflectionEmit(this IOpcUaServerBuilder)` extension
+swaps the registered factory descriptor over to Emit.
+
+---
+
+## 4. Implementation phases
+
+### Phase A — Create `Opc.Ua.ComplexTypes.Emit` project
+1. New project `Libraries/Opc.Ua.ComplexTypes.Emit/Opc.Ua.ComplexTypes.Emit.csproj`:
+ - `AssemblyName = $(AssemblyPrefix).ComplexTypes.Emit`
+ - `PackageId = $(PackagePrefix).Opc.Ua.ComplexTypes.Emit`
+ - `RootNamespace = Opc.Ua.ComplexTypes.Emit`
+ - `TargetFrameworks = $(LibxTargetFrameworks)`
+ - **No** `IsAotCompatible` (this package is the non-AOT path)
+ - `Nullable = enable`
+ - References `Opc.Ua.Core` and `Opc.Ua.ComplexTypes`
+ - `InternalsVisibleTo` for `Opc.Ua.Client.ComplexTypes`, `Opc.Ua.Server.ComplexTypes`, `Opc.Ua.Client.ComplexTypes.Tests`
+2. `Properties/AssemblyInfo.cs` carries `[assembly: CLSCompliant(false)]`.
+3. Add to `UA.slnx` under `/Libraries/`.
+
+### Phase B — Move files into the Emit project
+1. Move from `Libraries/Opc.Ua.ComplexTypes/Builders/`:
+ - `AssemblyModule.cs`, `ComplexTypeBuilder.cs`, `ComplexTypeBuilderFactory.cs`, `ComplexTypeFieldBuilder.cs`, `AttributeExtensions.cs`
+ → `Libraries/Opc.Ua.ComplexTypes.Emit/Builders/`
+2. Move the entire `Libraries/Opc.Ua.ComplexTypes/Types/` directory → `Libraries/Opc.Ua.ComplexTypes.Emit/Types/`.
+3. Update namespace on every moved file: `Opc.Ua.ComplexTypes` → `Opc.Ua.ComplexTypes.Emit`.
+4. Add `using Opc.Ua.ComplexTypes;` to the Emit files that reference the interfaces / `DataTypeNotSupportedException` / etc. from the core package.
+5. Re-evaluate `InternalsVisibleTo` on `Opc.Ua.ComplexTypes.csproj`: drop the `Opc.Ua.Client.ComplexTypes` grant if no internal access remains needed; keep `Opc.Ua.Client` if the moved-to-Client orchestrators still need any internal access.
+
+### Phase C — Update `Opc.Ua.Client.ComplexTypes`
+1. Add `` ONLY if we want the existing DI default to keep working without forcing apps to add the Emit package. **Recommended: don't add it** — the existing `AddComplexTypes()` switches to Default, and apps that want Emit add the Emit package and call the new extension.
+2. Update `ComplexTypeSystemFactory.Create(...)` to use `new DefaultComplexTypeFactory()` instead of `new ComplexTypeBuilderFactory()`.
+3. Delete the existing `ComplexTypesExtensions.cs` (the `Create(session)` / `Create(resolver)` extension methods used Emit) — move to the Emit package as `ComplexTypesEmitExtensions.cs` with renamed methods (`CreateWithReflectionEmit`).
+4. Update `using` statements where the file referenced moved types.
+
+### Phase D — Update `Opc.Ua.Server.ComplexTypes`
+1. No project-reference change required (it already defaults to Default).
+2. Add a sibling `OpcUaComplexTypesServerBuilderEmitExtensions.cs` to **the Emit package** that re-registers `ServerComplexTypeSystemFactory` with an Emit factory descriptor.
+
+### Phase E — DI extensions in the Emit package
+1. New file `Libraries/Opc.Ua.ComplexTypes.Emit/Hosting/OpcUaComplexTypesEmitBuilderExtensions.cs`:
+ - `public static IOpcUaBuilder AddComplexTypesWithReflectionEmit(this IOpcUaBuilder builder)` — calls `AddComplexTypes()` and then `builder.Services.Replace(ServiceDescriptor.Singleton(sp => new ComplexTypeSystemFactory(sp.GetRequiredService(), useReflectionEmit: true)))`. (Either add an internal ctor overload to `ComplexTypeSystemFactory` or expose a `WithFactory(Func)` setter.)
+ - Same for the server: `IOpcUaServerBuilder.AddComplexTypesWithReflectionEmit()`.
+2. New file `Libraries/Opc.Ua.ComplexTypes.Emit/ComplexTypesEmitExtensions.cs`:
+ - Moves the `Create(session, telemetry)` / `Create(resolver, telemetry)` extensions on `ComplexTypeSystem`, renamed to `CreateWithReflectionEmit` to match the DI naming.
+
+### Phase F — Update consumers
+1. **Apps and tests**: the only in-repo consumers that name the moved symbols today are:
+ - `Tests/Opc.Ua.Client.ComplexTypes.Tests/*` — exercises Emit. Add `using Opc.Ua.ComplexTypes.Emit;` and add a ``.
+ - `Tests/Opc.Ua.Aot.Tests/ComplexTypeAotTests.cs` — uses only Default; **no change**.
+ - `Applications/ConsoleReferenceClient/{Program.cs, ClientSamples.cs}` — currently uses `new ComplexTypeBuilderFactory()` via `using Opc.Ua.ComplexTypes;`. Switch to `using Opc.Ua.ComplexTypes.Emit;` and add the Emit project reference (the sample wants Emit-generated types for live demo).
+ - `Tests/Opc.Ua.Client.Tests/ComplexTypes/Default*Tests.cs` — uses only Default; **no change**.
+2. Run a repo-wide grep for `ComplexTypeBuilderFactory|BaseComplexType|OptionalFieldsComplexType|UnionComplexType|StructureDefinitionAttribute|StructureFieldAttribute|StructureTypeIdAttribute|ComplexTypePropertyInfo|IComplexTypeProperties|AssemblyModule|AttributeExtensions` to confirm.
+
+### Phase G — Build + test gate
+1. `dotnet build UA.slnx` clean (TreatWarningsAsErrors).
+2. Run:
+ - `dotnet test Tests/Opc.Ua.Client.ComplexTypes.Tests` (Emit path, 3396 tests).
+ - `dotnet test Tests/Opc.Ua.Client.Tests --filter "FullyQualifiedName~ComplexTypes"` (default path, 625 tests).
+ - The AOT tests (`Tests/Opc.Ua.Aot.Tests`) build under the .NET 10 test platform — confirm they don't pull in the Emit package transitively. A clean AOT-test artifact graph is the proof that the split achieves its goal.
+
+### Phase H — Documentation
+1. Update `OpcComplexTypeSystemOverhaul.md` → note that the architecture is now four projects (`ComplexTypes`, `ComplexTypes.Emit`, `Client.ComplexTypes`, `Server.ComplexTypes`).
+2. Append a new section to `BreakingChanges.md` covering this second wave (see §6).
+3. `Docs/ComplexTypes.md` — explain when to use which package (decision flow: AOT? → core only. Need runtime concrete classes? → add Emit. Default DI flow now uses non-Emit; opt in via `AddComplexTypesWithReflectionEmit()`).
+4. `Docs/MigrationGuide.md` — append the `using Opc.Ua.ComplexTypes.Emit;` requirement and DI rename.
+
+---
+
+## 5. Decision points worth confirming before implementation
+
+| Question | Recommendation |
+| --- | --- |
+| **Package name** — `.Emit` vs `.Reflection.Emit` vs `.Dynamic`. | **`.Emit`** — short, matches `System.Reflection.Emit` mental model, avoids the word "Legacy". |
+| **Default DI behaviour** — should `AddComplexTypes()` keep its meaning (Emit, today) or flip to Default? | **Flip to Default.** AOT must be the default in a 1.6 ecosystem; users that need Emit explicitly opt in. This IS a behavioural breaking change — call it out clearly in `BreakingChanges.md`. |
+| **Keep `BaseComplexType` etc. in core** for forward-compat reasons? | **No.** They're only useful in the Emit pipeline; keeping them in core forces AOT builds to ship type-metadata they never use. Move them. |
+| **Attributes** (`StructureDefinitionAttribute`, etc.) — keep in core or move to Emit? | **Move to Emit.** Their only consumers are the Emit builders, `BaseComplexType` reflection, and Emit tests. The source generator does not use them. |
+| **Single combined namespace** (`Opc.Ua.ComplexTypes`) preserved by `[Forward...]`? | **No.** Consistent with the previous overhaul's "no forwarders" decision. |
+
+If any answer above flips during review, the plan adapts mechanically — e.g. keeping attributes in core would just remove one file move and leave the Emit package with a dependency-only relationship to those classes.
+
+---
+
+## 6. Additional breaking changes (to append to `BreakingChanges.md`)
+
+When this plan ships, the `BreakingChanges.md` file gets a new section. Preview:
+
+### Wave 2: AOT/Emit split
+
+**Namespace moves** — from `Opc.Ua.ComplexTypes` to **`Opc.Ua.ComplexTypes.Emit`**:
+- `BaseComplexType`, `OptionalFieldsComplexType`, `UnionComplexType`
+- `ComplexTypePropertyInfo`, `IComplexTypeProperties`
+- `StructureDefinitionAttribute`, `StructureFieldAttribute`, `StructureTypeIdAttribute`, `StructureBaseDataType`
+- `ComplexTypeBuilder`, `ComplexTypeBuilderFactory`, `ComplexTypeFieldBuilder`
+- `AssemblyModule`, `AttributeExtensions`
+
+**Assembly redistribution** — these types now ship in `Opc.Ua.ComplexTypes.Emit.dll` instead of `Opc.Ua.ComplexTypes.dll`. Reflection by assembly-qualified name breaks; recompile.
+
+**New required NuGet package** for consumers that want Emit-generated runtime types: `OPCFoundation.NetStandard.Opc.Ua.ComplexTypes.Emit`. It is **not** pulled in transitively by `Opc.Ua.Client.ComplexTypes` — explicit opt-in.
+
+**DI default flip**:
+- `IOpcUaBuilder.AddComplexTypes()` now registers a `ComplexTypeSystemFactory` bound to `DefaultComplexTypeFactory` (AOT-friendly). Previously it bound to `ComplexTypeBuilderFactory` (Emit).
+- To keep the prior behaviour, add the new Emit package and call `IOpcUaBuilder.AddComplexTypesWithReflectionEmit()` instead.
+
+**Static factory rename**:
+- `ComplexTypeSystem.Create(ISession, ITelemetryContext)` (the Emit-producing extension) moves to `Opc.Ua.ComplexTypes.Emit` and is renamed to `ComplexTypeSystem.CreateWithReflectionEmit(ISession, ITelemetryContext)`.
+- A new `ComplexTypeSystem.Create(ISession, ITelemetryContext)` extension shipping in `Opc.Ua.Client.ComplexTypes` returns a Default-backed instance.
+
+**`InternalsVisibleTo` re-shuffle** — same pattern as the first overhaul; details land with the implementation.
+
+---
+
+## 7. Out of scope
+
+- Splitting `Opc.Ua.Client.ComplexTypes` further (e.g. an `Opc.Ua.Client.ComplexTypes.Emit`). The orchestration logic in `ComplexTypeSystem` works with any `IComplexTypeFactory`; no per-path duplication needed.
+- Source-generator changes. The generator's output is independent of this split.
+- Performance work, encoder changes, or any non-packaging refactor.
+- Backwards-compatibility shims.
diff --git a/Tests/Opc.Ua.Aot.Tests/ComplexTypeAotTests.cs b/Tests/Opc.Ua.Aot.Tests/ComplexTypeAotTests.cs
index cd25dbdfec..8006c1a1fa 100644
--- a/Tests/Opc.Ua.Aot.Tests/ComplexTypeAotTests.cs
+++ b/Tests/Opc.Ua.Aot.Tests/ComplexTypeAotTests.cs
@@ -28,6 +28,7 @@
* ======================================================================*/
using Opc.Ua.Client;
+using Opc.Ua.ComplexTypes;
using Opc.Ua.Client.ComplexTypes;
namespace Opc.Ua.Aot.Tests
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs
index 12080f8c7a..6ba67b40c4 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs
@@ -34,6 +34,7 @@
using BenchmarkDotNet.Attributes;
using NUnit.Framework;
using Opc.Ua.Client;
+using Opc.Ua.ComplexTypes;
using Opc.Ua.Client.ComplexTypes;
using Opc.Ua.Server.Tests;
using Quickstarts.ReferenceServer;
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs
index 013f4214a1..d5b375fa1e 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs
@@ -29,6 +29,7 @@
using NUnit.Framework;
using Opc.Ua.Client;
+using Opc.Ua.ComplexTypes;
using Opc.Ua.Client.ComplexTypes;
using ComplexStructure = Opc.Ua.Encoders.Structure;
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs
index 9a7797b2a1..9c893327d8 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs
@@ -35,6 +35,7 @@
using System.Xml;
using NUnit.Framework;
using Opc.Ua.Client;
+using Opc.Ua.ComplexTypes;
using Opc.Ua.Client.ComplexTypes;
using Opc.Ua.Core.Encoders.Tests;
using Opc.Ua.Test;
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs
index 76ff881d24..0c6488f0f0 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs
@@ -30,6 +30,7 @@
using System.Xml;
using NUnit.Framework;
using Opc.Ua.Client;
+using Opc.Ua.ComplexTypes;
using Opc.Ua.Client.ComplexTypes;
namespace Opc.Ua.Client.ComplexTypes.Tests
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs
index ec2e1154ee..9402dea11d 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs
@@ -31,6 +31,7 @@
using System.Xml;
using NUnit.Framework;
using Opc.Ua.Client;
+using Opc.Ua.ComplexTypes;
using Opc.Ua.Client.ComplexTypes;
using Opc.Ua.Tests;
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Hosting/AddComplexTypesBuilderTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Hosting/AddComplexTypesBuilderTests.cs
index ee73633cad..6a15e799dd 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Hosting/AddComplexTypesBuilderTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Hosting/AddComplexTypesBuilderTests.cs
@@ -30,6 +30,8 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
+using Opc.Ua.ComplexTypes;
+
namespace Opc.Ua.Client.ComplexTypes.Tests.Hosting
{
///
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/LeakDetectionSetup.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/LeakDetectionSetup.cs
index e101c331e3..5b82d70ac5 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/LeakDetectionSetup.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/LeakDetectionSetup.cs
@@ -32,6 +32,8 @@
using Opc.Ua.Security.Certificates;
using Opc.Ua.Tests;
+using Opc.Ua.ComplexTypes;
+
namespace Opc.Ua.Client.ComplexTypes.Tests
{
///
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs
index ca7c528776..a8b783f01c 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs
@@ -32,6 +32,7 @@
using Microsoft.Extensions.Logging;
using NUnit.Framework;
using Opc.Ua.Client;
+using Opc.Ua.ComplexTypes;
using Opc.Ua.Client.ComplexTypes;
using Opc.Ua.Client.TestFramework;
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj
index aa3280c322..b89ec16f58 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj
@@ -30,6 +30,7 @@
+
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs
index bb294f48c4..3a8ac6935d 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs
@@ -45,6 +45,9 @@
using Opc.Ua.Client.TestFramework;
using Opc.Ua.Server.TestFramework;
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.ComplexTypes.Emit;
+
namespace Opc.Ua.Client.ComplexTypes.Tests
{
///
@@ -168,7 +171,7 @@ public async Task LoadTypeSystemAsync(
bool disableDataTypeDefinition,
bool disableDataTypeDictionary)
{
- var typeSystem = ComplexTypeSystem.Create(Session, m_telemetry);
+ var typeSystem = ComplexTypeSystem.CreateWithReflectionEmit(Session, m_telemetry);
Assert.That(typeSystem, Is.Not.Null);
typeSystem.DisableDataTypeDefinition = disableDataTypeDefinition;
typeSystem.DisableDataTypeDictionary = disableDataTypeDictionary;
@@ -211,7 +214,7 @@ public async Task LoadTypeSystemAsync(
public async Task BrowseComplexTypesServerAsync()
{
var samples = new ClientSamples(m_telemetry, null, null, true);
- var complexTypeSystem = ComplexTypeSystem.Create(Session, m_telemetry);
+ var complexTypeSystem = ComplexTypeSystem.CreateWithReflectionEmit(Session, m_telemetry);
await samples.LoadTypeSystemAsync(complexTypeSystem, default).ConfigureAwait(false);
ArrayOf referenceDescriptions = await samples
@@ -249,7 +252,7 @@ public async Task BrowseComplexTypesServerAsync()
public async Task FetchComplexTypesServerAsync()
{
var samples = new ClientSamples(m_telemetry, null, null, true);
- var complexTypeSystem = ComplexTypeSystem.Create(Session, m_telemetry);
+ var complexTypeSystem = ComplexTypeSystem.CreateWithReflectionEmit(Session, m_telemetry);
await samples.LoadTypeSystemAsync(complexTypeSystem, default).ConfigureAwait(false);
IList allNodes = await samples
@@ -406,7 +409,7 @@ public void ValidateFetchedAndBrowsedNodesMatch()
public async Task ReadWriteScalarVariableTypeAsync()
{
var samples = new ClientSamples(m_telemetry, null, null, true);
- var complexTypeSystem = ComplexTypeSystem.Create(Session, m_telemetry);
+ var complexTypeSystem = ComplexTypeSystem.CreateWithReflectionEmit(Session, m_telemetry);
await samples.LoadTypeSystemAsync(complexTypeSystem, default).ConfigureAwait(false);
// test the static version of the structure
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs
index d24da5edc2..f1323b6588 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs
@@ -39,6 +39,9 @@
using Opc.Ua.Core.TestFramework;
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.ComplexTypes.Emit;
+
namespace Opc.Ua.Client.ComplexTypes.Tests.Types
{
///
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesTests.cs
index 11848a2677..85ed81bfb1 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesTests.cs
@@ -32,6 +32,9 @@
using Opc.Ua.Core.TestFramework;
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.ComplexTypes.Emit;
+
namespace Opc.Ua.Client.ComplexTypes.Tests.Types
{
///
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/EncoderTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/EncoderTests.cs
index f75c96fc81..f4cab1f40c 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/EncoderTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/EncoderTests.cs
@@ -37,6 +37,9 @@
using Opc.Ua.Core.TestFramework;
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.ComplexTypes.Emit;
+
namespace Opc.Ua.Client.ComplexTypes.Tests.Types
{
///
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs
index 983410b278..e3cb149cdd 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs
@@ -40,6 +40,9 @@
using Opc.Ua.Core.TestFramework;
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.ComplexTypes.Emit;
+
namespace Opc.Ua.Client.ComplexTypes.Tests.Types
{
///
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs
index de471c1396..fedaaad34e 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs
@@ -32,6 +32,9 @@
using System.Threading;
using System.Threading.Tasks;
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.ComplexTypes.Emit;
+
namespace Opc.Ua.Client.ComplexTypes.Tests.Types
{
///
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs
index 87890f7f34..71168ebf2e 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs
@@ -41,6 +41,9 @@
using Opc.Ua.Core.TestFramework;
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.ComplexTypes.Emit;
+
namespace Opc.Ua.Client.ComplexTypes.Tests.Types
{
///
@@ -274,7 +277,7 @@ public async Task CreateMockTypeAsync(
// add type
mockResolver.DataTypeNodes[dataTypeNode.NodeId] = dataTypeNode;
- var cts = ComplexTypeSystem.Create(mockResolver, telemetry);
+ var cts = ComplexTypeSystem.CreateWithReflectionEmit(mockResolver, telemetry);
IType carType = await cts.LoadTypeAsync(dataTypeNode.NodeId, false, true)
.ConfigureAwait(false);
Assert.That(carType, Is.Not.Null);
@@ -473,7 +476,7 @@ public async Task CreateMockArrayTypeAsync(
// add types needed
mockResolver.DataTypeNodes[dataTypeNode.NodeId] = dataTypeNode;
- var cts = ComplexTypeSystem.Create(mockResolver, telemetry);
+ var cts = ComplexTypeSystem.CreateWithReflectionEmit(mockResolver, telemetry);
IType arraysTypes = await cts.LoadTypeAsync(dataTypeNode.NodeId, false, true)
.ConfigureAwait(false);
Assert.That(arraysTypes, Is.Not.Null);
@@ -664,7 +667,7 @@ public async Task CreateMockSingleTypeAsync(
// add types needed
mockResolver.DataTypeNodes[dataTypeNode.NodeId] = dataTypeNode;
- var cts = ComplexTypeSystem.Create(mockResolver, telemetry);
+ var cts = ComplexTypeSystem.CreateWithReflectionEmit(mockResolver, telemetry);
IType arraysTypes = await cts.LoadTypeAsync(dataTypeNode.NodeId, false, true)
.ConfigureAwait(false);
Assert.That(arraysTypes, Is.Not.Null);
diff --git a/Tests/Opc.Ua.Client.Tests/ComplexTypes/TypeSystemClientTest.cs b/Tests/Opc.Ua.Client.Tests/ComplexTypes/TypeSystemClientTest.cs
new file mode 100644
index 0000000000..68ea090c62
--- /dev/null
+++ b/Tests/Opc.Ua.Client.Tests/ComplexTypes/TypeSystemClientTest.cs
@@ -0,0 +1,499 @@
+/* ========================================================================
+ * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
+ *
+ * OPC Foundation MIT License 1.00
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The complete license agreement can be found here:
+ * http://opcfoundation.org/License/MIT/1.00/
+ * ======================================================================*/
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml;
+using NUnit.Framework;
+using Opc.Ua.ComplexTypes;
+using Opc.Ua.Client.ComplexTypes;
+using Opc.Ua.Server.Tests;
+using Opc.Ua.Tests;
+using Quickstarts;
+using Quickstarts.ReferenceServer;
+
+using Opc.Ua.Client.TestFramework;
+using Opc.Ua.Server.TestFramework;
+
+namespace Opc.Ua.Client.Tests.ComplexTypes
+{
+ ///
+ /// Load Type System tests.
+ ///
+ [TestFixture]
+ [Category("Client")]
+ [SetCulture("en-us")]
+ [SetUICulture("en-us")]
+ [NonParallelizable]
+ public class TypeSystemClientTest : IUAClient
+ {
+ public ISession Session { get; private set; }
+ private ServerFixture m_serverFixture;
+#pragma warning disable NUnit1032 // An IDisposable field/property should be Disposed in a TearDown method
+ private ClientFixture m_clientFixture;
+#pragma warning restore NUnit1032 // An IDisposable field/property should be Disposed in a TearDown method
+ private ReferenceServer m_server;
+ private readonly string m_uriScheme;
+ private ITelemetryContext m_telemetry;
+ private string m_pkiRoot;
+ private Uri m_url;
+
+ ///
+ /// for test that fetched and browsed node count match
+ ///
+ private int m_fetchedNodesCount;
+ private int m_browsedNodesCount;
+
+ public TypeSystemClientTest()
+ {
+ m_uriScheme = Utils.UriSchemeOpcTcp;
+ }
+
+ public TypeSystemClientTest(string uriScheme)
+ {
+ m_uriScheme = uriScheme;
+ }
+
+ ///
+ /// Set up a Server and a Client instance.
+ ///
+ [OneTimeSetUp]
+ public Task OneTimeSetUpAsync()
+ {
+ m_fetchedNodesCount = -1;
+ m_browsedNodesCount = -1;
+
+ return OneTimeSetUpAsync(NUnitTelemetryContext.Create());
+ }
+
+ ///
+ /// Setup a server and client fixture.
+ ///
+ /// The telemetry context to use to create obvservability instruments
+ private async Task OneTimeSetUpAsync(ITelemetryContext telemetry)
+ {
+ // pki directory root for test runs.
+ m_telemetry = telemetry;
+ m_pkiRoot = Path.GetTempPath() + Path.GetRandomFileName();
+
+ // start Ref server
+ m_serverFixture = new ServerFixture(t => new ReferenceServer(t))
+ {
+ UriScheme = m_uriScheme,
+ SecurityNone = true,
+ AutoAccept = true,
+ AllNodeManagers = true,
+ OperationLimits = true
+ };
+
+ m_server = await m_serverFixture.StartAsync(m_pkiRoot)
+ .ConfigureAwait(false);
+
+ m_clientFixture = new ClientFixture(telemetry);
+
+ await m_clientFixture.LoadClientConfigurationAsync(m_pkiRoot).ConfigureAwait(false);
+ m_clientFixture.Config.TransportQuotas.MaxMessageSize = 4 * 1024 * 1024;
+ m_url = new Uri(
+ m_uriScheme +
+ "://localhost:" +
+ m_serverFixture.Port.ToString(CultureInfo.InvariantCulture));
+ try
+ {
+ Session = await m_clientFixture
+ .ConnectAsync(m_url, SecurityPolicies.Basic256Sha256)
+ .ConfigureAwait(false);
+ }
+ catch (Exception e)
+ {
+ Assert.Ignore(
+ $"OneTimeSetup failed to create session, tests skipped. Error: {e.Message}");
+ }
+ }
+
+ ///
+ /// Tear down the Server and the Client.
+ ///
+ [OneTimeTearDown]
+ public async Task OneTimeTearDownAsync()
+ {
+ if (Session != null)
+ {
+ await Session.CloseAsync().ConfigureAwait(false);
+ Session.Dispose();
+ Session = null;
+ }
+ await m_serverFixture.StopAsync().ConfigureAwait(false);
+ m_clientFixture?.Dispose();
+ m_server?.Dispose();
+ }
+
+ [Test]
+ [Order(100)]
+ [TestCase(false, false, false)]
+ [TestCase(true, false, false)]
+ [TestCase(false, true, false)]
+ [TestCase(false, false, true)]
+ public async Task LoadTypeSystemAsync(
+ bool onlyEnumTypes,
+ bool disableDataTypeDefinition,
+ bool disableDataTypeDictionary)
+ {
+ var typeSystem = new ComplexTypeSystem(Session, m_telemetry);
+ Assert.That(typeSystem, Is.Not.Null);
+ typeSystem.DisableDataTypeDefinition = disableDataTypeDefinition;
+ typeSystem.DisableDataTypeDictionary = disableDataTypeDictionary;
+
+ bool success = await typeSystem.LoadAsync(onlyEnumTypes, true).ConfigureAwait(false);
+ Assert.That(success, Is.True);
+
+ IReadOnlyList types = typeSystem.GetDefinedTypes();
+ TestContext.Out.WriteLine("Types loaded: {0} ", types.Count);
+ foreach (XmlQualifiedName type in types)
+ {
+ TestContext.Out.WriteLine("Type: {0} ", type);
+ }
+
+ foreach (ExpandedNodeId dataTypeId in typeSystem.GetDefinedDataTypeIds())
+ {
+ NodeIdDictionary definitions =
+ typeSystem.GetDataTypeDefinitionsForDataType(dataTypeId);
+ Assert.That(definitions, Is.Not.Empty);
+ Assert.That(Session.Factory.TryGetType(dataTypeId, out IType type), Is.True);
+
+ var localTypeId = ExpandedNodeId.ToNodeId(dataTypeId, Session.NamespaceUris);
+ if (type is IEnumeratedType)
+ {
+ Assert.That(definitions, Has.Count.EqualTo(1));
+ Assert.That(definitions.First().Value, Is.InstanceOf());
+ Assert.That(definitions.First().Key, Is.EqualTo(localTypeId));
+ }
+ else
+ {
+ Assert.That(definitions[localTypeId], Is.InstanceOf());
+ }
+ }
+ }
+
+ [Test]
+ [Order(200)]
+ public async Task BrowseComplexTypesServerAsync()
+ {
+ var samples = new ClientSamples(m_telemetry, null, null, true);
+ var complexTypeSystem = new ComplexTypeSystem(Session, m_telemetry);
+ await samples.LoadTypeSystemAsync(complexTypeSystem, default).ConfigureAwait(false);
+
+ ArrayOf referenceDescriptions = await samples
+ .BrowseFullAddressSpaceAsync(this, ObjectIds.RootFolder)
+ .ConfigureAwait(false);
+
+ TestContext.Out.WriteLine("References: {0}", referenceDescriptions.Count);
+ m_browsedNodesCount = referenceDescriptions.Count;
+
+ ArrayOf variableIds =
+ referenceDescriptions
+ .Filter(r => r.NodeClass == NodeClass.Variable)
+ .ConvertAll(r => ExpandedNodeId.ToNodeId(r.NodeId, Session.NamespaceUris));
+
+ TestContext.Out.WriteLine("VariableIds: {0}", variableIds.Count);
+
+ (ArrayOf values, ArrayOf serviceResults) =
+ await samples.ReadAllValuesAsync(this, variableIds).ConfigureAwait(false);
+
+ int ii = 0;
+ foreach (ServiceResult serviceResult in serviceResults)
+ {
+ ServiceResult result = serviceResults[ii++];
+ Assert.That(
+ ServiceResult.IsGood(serviceResult) ||
+ serviceResult.StatusCode == StatusCodes.BadNotReadable ||
+ serviceResult.StatusCode == StatusCodes.BadUserAccessDenied,
+ Is.True,
+ $"Expected good result, but received {serviceResult}");
+ }
+ }
+
+ [Test]
+ [Order(300)]
+ public async Task FetchComplexTypesServerAsync()
+ {
+ var samples = new ClientSamples(m_telemetry, null, null, true);
+ var complexTypeSystem = new ComplexTypeSystem(Session, m_telemetry);
+ await samples.LoadTypeSystemAsync(complexTypeSystem, default).ConfigureAwait(false);
+
+ IList allNodes = await samples
+ .FetchAllNodesNodeCacheAsync(this, ObjectIds.RootFolder, true, false, false)
+ .ConfigureAwait(false);
+
+ TestContext.Out.WriteLine("References: {0}", allNodes.Count);
+
+ m_fetchedNodesCount = allNodes.Count;
+
+ var variableIds =
+ allNodes
+ .Where(r =>
+ r.NodeClass == NodeClass.Variable &&
+ r is VariableNode v &&
+ v.DataType.NamespaceIndex != 0)
+ .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, Session.NamespaceUris))
+ .ToList();
+
+ TestContext.Out.WriteLine("VariableIds: {0}", variableIds.Count);
+
+ (ArrayOf values, ArrayOf serviceResults) =
+ await samples.ReadAllValuesAsync(this, variableIds).ConfigureAwait(false);
+
+ foreach (ServiceResult serviceResult in serviceResults)
+ {
+ Assert.That(ServiceResult.IsGood(serviceResult), Is.True, serviceResult.ToString());
+ }
+
+ // check if complex type is properly decoded
+ bool testFailed = false;
+ for (int ii = 0; ii < values.Count; ii++)
+ {
+ DataValue value = values[ii];
+ NodeId variableId = variableIds[ii];
+ var variableExpandedNodeId = NodeId.ToExpandedNodeId(
+ variableId,
+ Session.NamespaceUris);
+ if (allNodes.FirstOrDefault(
+ n => n.NodeId == variableId) is VariableNode variableNode &&
+ variableNode.DataType.NamespaceIndex != 0)
+ {
+ TestContext.Out.WriteLine("Check for custom type: {0}", variableNode);
+ var fullTypeId = NodeId.ToExpandedNodeId(
+ variableNode.DataType,
+ Session.NamespaceUris);
+ if (!Session.Factory.TryGetType(fullTypeId, out IType type))
+ {
+ // check for opaque type
+ NodeId superType =
+ await Session.NodeCache.FindSuperTypeAsync(fullTypeId).ConfigureAwait(false);
+ NodeId lastGoodType = variableNode.DataType;
+ while (!superType.IsNull && superType != DataTypes.BaseDataType)
+ {
+ if (superType == DataTypeIds.Structure)
+ {
+ testFailed = true;
+ break;
+ }
+ lastGoodType = superType;
+ superType =
+ await Session.NodeCache.FindSuperTypeAsync(superType).ConfigureAwait(false);
+ }
+
+ if (testFailed)
+ {
+ TestContext.Out.WriteLine(
+ "-- Variable: {0} complex type unavailable --> {1}",
+ variableNode.NodeId,
+ variableNode.DataType);
+ (_, _) = await samples.ReadAllValuesAsync(this, [variableId])
+ .ConfigureAwait(false);
+ }
+ else
+ {
+ TestContext.Out.WriteLine(
+ "-- Variable: {0} opaque typeid --> {1}",
+ variableNode.NodeId,
+ lastGoodType);
+ }
+ continue;
+ }
+
+ if (value.WrappedValue.TryGetValue(out ExtensionObject extensionObject) &&
+ extensionObject.TryGetValue(out IEncodeable encodeable))
+ {
+ if (!Session.Factory.TryGetType(encodeable.TypeId, out IType valueType) ||
+ valueType.XmlName != type.XmlName)
+ {
+ testFailed = true;
+ TestContext.Out.WriteLine(
+ "Variable: {0} type is decoded as ExtensionObject --> {1}",
+ variableNode,
+ value.WrappedValue);
+ (_, _) = await samples.ReadAllValuesAsync(this, [variableId])
+ .ConfigureAwait(false);
+ }
+ continue;
+ }
+
+ if (value.WrappedValue.TryGetValue(out ArrayOf array))
+ {
+ foreach (ExtensionObject valueItem in array.ToList())
+ {
+ if (valueItem.TryGetValue(out encodeable))
+ {
+ if (!Session.Factory.TryGetType(encodeable.TypeId, out IType valueType) ||
+ valueType.XmlName != type.XmlName)
+ {
+ testFailed = true;
+ TestContext.Out.WriteLine(
+ "Variable: {0} type is decoded as ExtensionObject --> {1}",
+ variableNode,
+ valueItem);
+ (_, _) = await samples.ReadAllValuesAsync(this, [variableId])
+ .ConfigureAwait(false);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (testFailed)
+ {
+ Assert.Fail(
+ "Test failed, unknown or undecodable complex type detected. See log for information.");
+ }
+ }
+
+ [Test]
+ [Order(330)]
+ public void ValidateFetchedAndBrowsedNodesMatch()
+ {
+ if (m_browsedNodesCount < 0 || m_fetchedNodesCount < 0)
+ {
+ Assert.Ignore("The browse or fetch test did not run.");
+ }
+ // The Browse and Fetch traversals run sequentially against a live
+ // server. Diagnostic and session-state nodes (e.g.
+ // Server.ServerDiagnostics.SessionDiagnosticsArray entries) can
+ // come and go between the two calls, so a small drift is
+ // expected. Anything more than a handful of nodes is a real
+ // structural mismatch.
+ Assert.That(
+ Math.Abs(m_browsedNodesCount - m_fetchedNodesCount),
+ Is.LessThanOrEqualTo(8),
+ "Browsed=" + m_browsedNodesCount + ", Fetched=" + m_fetchedNodesCount);
+ }
+
+ [Test]
+ [Order(400)]
+ public async Task ReadWriteScalarVariableTypeAsync()
+ {
+ var samples = new ClientSamples(m_telemetry, null, null, true);
+ var complexTypeSystem = new ComplexTypeSystem(Session, m_telemetry);
+ await samples.LoadTypeSystemAsync(complexTypeSystem, default).ConfigureAwait(false);
+
+ // test the static version of the structure
+ ExpandedNodeId structureVariable = TestData.VariableIds
+ .Data_Static_Structure_ScalarStructure;
+ Assert.That(structureVariable.IsNull, Is.False);
+ var nodeId = ExpandedNodeId.ToNodeId(structureVariable, Session.NamespaceUris);
+ Assert.That(nodeId.IsNull, Is.False);
+ Node node = await Session.ReadNodeAsync(nodeId).ConfigureAwait(false);
+ Assert.That(node, Is.Not.Null);
+ Assert.That(node, Is.InstanceOf());
+ DataValue dataValue = await Session.ReadValueAsync(nodeId).ConfigureAwait(false);
+ Assert.That(dataValue.IsNull, Is.False);
+
+ // test the accessor to the complex types
+ Assert.That(dataValue.WrappedValue.TryGetValue(out ExtensionObject extensionObject), Is.True);
+ Assert.That(extensionObject.TryGetValue(out IEncodeable encodeable), Is.True);
+ Assert.That(encodeable, Is.Not.Null);
+ var complexType = encodeable as IStructure;
+ Assert.That(complexType, Is.Not.Null);
+
+ // list properties
+ TestContext.Out.WriteLine("{0} Properties", complexType.GetFields().Count);
+ foreach (IStructureField property in complexType.GetFields())
+ {
+ TestContext.Out.WriteLine(
+ "{0} (Type: {1}): {2})",
+ property.Name,
+ complexType[property.Name].TypeInfo,
+ complexType[property.Name].ToString());
+ }
+
+ complexType["ByteValue"] = (byte)0;
+ complexType["StringValue"] = "badbeef";
+ complexType["NumberValue"] = new Variant((uint)3210);
+ complexType["IntegerValue"] = new Variant((long)54321);
+ complexType["UIntegerValue"] = new Variant((ulong)12345);
+
+ var dataWriteValue = new DataValue(
+ dataValue.WrappedValue,
+ StatusCodes.Good,
+ DateTime.UtcNow);
+
+ // write value back
+ ArrayOf writeValues =
+ [
+ new WriteValue
+ {
+ NodeId = nodeId,
+ AttributeId = Attributes.Value,
+ Value = dataWriteValue
+ }
+ ];
+
+ WriteResponse response = await Session
+ .WriteAsync(null, writeValues, CancellationToken.None)
+ .ConfigureAwait(false);
+ Assert.That(response, Is.Not.Null);
+ Assert.That(response.Results.IsNull, Is.False);
+ TestContext.Out.WriteLine(new ServiceResult(response.Results[0]).StatusCode);
+ TestContext.Out.WriteLine(response.Results[0].ToString());
+ Assert.That(StatusCode.IsGood(response.Results[0]), Is.True);
+
+ // read back written values
+ dataValue = await Session.ReadValueAsync(nodeId).ConfigureAwait(false);
+ Assert.That(dataValue.IsNull, Is.False);
+
+ Assert.That(dataValue.WrappedValue.TryGetValue(out extensionObject), Is.True);
+ Assert.That(extensionObject.TryGetValue(out encodeable), Is.True);
+ Assert.That(encodeable, Is.Not.Null);
+ complexType = encodeable as IStructure;
+ Assert.That(complexType, Is.Not.Null);
+
+ // list properties
+ TestContext.Out.WriteLine("{0} Properties", complexType.GetFields().Count);
+ foreach (IStructureField property in complexType.GetFields())
+ {
+ TestContext.Out.WriteLine(
+ "{0} (Type: {1}): {2})",
+ property.Name,
+ complexType[property.Name].TypeInfo,
+ complexType[property.Name].ToString());
+ }
+
+ Assert.That(complexType["ByteValue"], Is.EqualTo(new Variant((byte)0)));
+ Assert.That(complexType["StringValue"], Is.EqualTo(new Variant("badbeef")));
+ Assert.That(complexType["NumberValue"], Is.EqualTo(new Variant((uint)3210)));
+ Assert.That(complexType["IntegerValue"], Is.EqualTo(new Variant((long)54321)));
+ Assert.That(complexType["UIntegerValue"], Is.EqualTo(new Variant((ulong)12345)));
+ }
+ }
+}
diff --git a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj
index c06b9c7d6d..96a6bce92d 100644
--- a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj
+++ b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj
@@ -42,6 +42,8 @@
+
+
diff --git a/UA.slnx b/UA.slnx
index 6301965ac9..755e203a04 100644
--- a/UA.slnx
+++ b/UA.slnx
@@ -39,6 +39,8 @@
+
+
@@ -48,6 +50,7 @@
+