From b85fcd5eb7225aec668fa56e6055c7bec86c306c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Salih=20G=C3=B6nc=C3=BC?= Date: Thu, 28 May 2026 09:42:30 +0200 Subject: [PATCH 1/2] Initial commit of the complex type system overhaul --- .../ConsoleReferenceClient/ClientSamples.cs | 1 + .../ConsoleReferenceClient.csproj | 1 + .../ConsoleReferenceClient/Program.cs | 1 + .../ComplexTypeSystem.cs | 1 + .../ComplexTypeSystemFactory.cs | 1 + .../ComplexTypesExtensions.cs | 2 + .../NodeCacheResolver.cs | 1 + .../Opc.Ua.Client.ComplexTypes.csproj | 2 + .../readme.md | 0 .../Builders}/AssemblyModule.cs | 2 +- .../Builders}/AttributeExtensions.cs | 2 +- .../Builders}/ComplexTypeBuilder.cs | 2 +- .../Builders/ComplexTypeBuilderFactory.cs} | 2 +- .../Builders}/ComplexTypeFieldBuilder.cs | 2 +- .../Builders}/DefaultComplexTypeBuilder.cs | 2 +- .../Builders}/DefaultComplexTypeFactory.cs | 2 +- .../DefaultComplexTypeFieldBuilder.cs | 2 +- .../Builders}/IComplexTypeFactory.cs | 2 +- .../Builders}/IComplexTypeResolver.cs | 2 +- .../Exceptions}/DataTypeException.cs | 2 +- .../Opc.Ua.ComplexTypes.csproj | 27 + .../Properties/AssemblyInfo.cs | 32 ++ .../Schema}/DataDictionary.cs | 2 +- .../Schema}/DataTypeDefinitionExtension.cs | 2 +- .../Types/BaseComplexType.cs | 2 +- .../Types/ComplexTypePropertyInfo.cs | 2 +- .../Types/IComplexTypeProperties.cs | 2 +- .../Types/OptionalFieldsComplexType.cs | 2 +- .../Types/StructureDefinitionAttribute.cs | 2 +- .../Types/StructureFieldAttribute.cs | 2 +- .../Types/StructureTypeAttribute.cs | 2 +- .../Types/UnionComplexType.cs | 2 +- .../Opc.Ua.Server.ComplexTypes.csproj | 25 + ...pcUaComplexTypesServerBuilderExtensions.cs | 70 +++ .../Properties/AssemblyInfo.cs | 32 ++ .../ServerComplexTypeSystem.cs | 289 ++++++++++ .../ServerComplexTypeSystemFactory.cs | 103 ++++ OpcComplexTypeSystemOverhaul.md | 324 ++++++++++++ Tests/Opc.Ua.Aot.Tests/ComplexTypeAotTests.cs | 1 + .../DataDictionaryTests.cs | 4 + .../DefaultComplexTypesBuilderTests.cs | 4 + .../DefaultComplexTypesCommon.cs | 4 + .../DefaultEnumerationTests.cs | 4 + .../DefaultOptionSetTests.cs | 4 + .../Hosting/AddComplexTypesBuilderTests.cs | 2 + .../LeakDetectionSetup.cs | 2 + .../NodeCacheResolverTests.cs | 4 + .../TypeSystemClientTest.cs | 2 + .../Types/ComplexTypesCommon.cs | 2 + .../Types/ComplexTypesTests.cs | 2 + .../Types/EncoderTests.cs | 2 + .../Types/JsonEncoderTests.cs | 2 + .../Types/MockResolver.cs | 2 + .../Types/MockResolverTests.cs | 2 + .../ComplexTypes/TypeSystemClientTest.cs | 499 ++++++++++++++++++ .../Opc.Ua.Client.Tests.csproj | 2 + UA.slnx | 2 + 57 files changed, 1479 insertions(+), 21 deletions(-) rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.Client.ComplexTypes}/ComplexTypeSystem.cs (99%) rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.Client.ComplexTypes}/NodeCacheResolver.cs (99%) rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.Client.ComplexTypes}/readme.md (100%) rename Libraries/{Opc.Ua.Client.ComplexTypes/TypeBuilder => Opc.Ua.ComplexTypes/Builders}/AssemblyModule.cs (98%) rename Libraries/{Opc.Ua.Client.ComplexTypes/TypeBuilder => Opc.Ua.ComplexTypes/Builders}/AttributeExtensions.cs (99%) rename Libraries/{Opc.Ua.Client.ComplexTypes/TypeBuilder => Opc.Ua.ComplexTypes/Builders}/ComplexTypeBuilder.cs (99%) rename Libraries/{Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFactory.cs => Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilderFactory.cs} (99%) rename Libraries/{Opc.Ua.Client.ComplexTypes/TypeBuilder => Opc.Ua.ComplexTypes/Builders}/ComplexTypeFieldBuilder.cs (99%) rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.ComplexTypes/Builders}/DefaultComplexTypeBuilder.cs (99%) rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.ComplexTypes/Builders}/DefaultComplexTypeFactory.cs (98%) rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.ComplexTypes/Builders}/DefaultComplexTypeFieldBuilder.cs (99%) rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.ComplexTypes/Builders}/IComplexTypeFactory.cs (99%) rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.ComplexTypes/Builders}/IComplexTypeResolver.cs (99%) rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.ComplexTypes/Exceptions}/DataTypeException.cs (99%) create mode 100644 Libraries/Opc.Ua.ComplexTypes/Opc.Ua.ComplexTypes.csproj create mode 100644 Libraries/Opc.Ua.ComplexTypes/Properties/AssemblyInfo.cs rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.ComplexTypes/Schema}/DataDictionary.cs (99%) rename Libraries/{Opc.Ua.Client/ComplexTypes => Opc.Ua.ComplexTypes/Schema}/DataTypeDefinitionExtension.cs (99%) rename Libraries/{Opc.Ua.Client.ComplexTypes => Opc.Ua.ComplexTypes}/Types/BaseComplexType.cs (99%) rename Libraries/{Opc.Ua.Client.ComplexTypes => Opc.Ua.ComplexTypes}/Types/ComplexTypePropertyInfo.cs (99%) rename Libraries/{Opc.Ua.Client.ComplexTypes => Opc.Ua.ComplexTypes}/Types/IComplexTypeProperties.cs (98%) rename Libraries/{Opc.Ua.Client.ComplexTypes => Opc.Ua.ComplexTypes}/Types/OptionalFieldsComplexType.cs (99%) rename Libraries/{Opc.Ua.Client.ComplexTypes => Opc.Ua.ComplexTypes}/Types/StructureDefinitionAttribute.cs (98%) rename Libraries/{Opc.Ua.Client.ComplexTypes => Opc.Ua.ComplexTypes}/Types/StructureFieldAttribute.cs (98%) rename Libraries/{Opc.Ua.Client.ComplexTypes => Opc.Ua.ComplexTypes}/Types/StructureTypeAttribute.cs (98%) rename Libraries/{Opc.Ua.Client.ComplexTypes => Opc.Ua.ComplexTypes}/Types/UnionComplexType.cs (99%) create mode 100644 Libraries/Opc.Ua.Server.ComplexTypes/Opc.Ua.Server.ComplexTypes.csproj create mode 100644 Libraries/Opc.Ua.Server.ComplexTypes/OpcUaComplexTypesServerBuilderExtensions.cs create mode 100644 Libraries/Opc.Ua.Server.ComplexTypes/Properties/AssemblyInfo.cs create mode 100644 Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystem.cs create mode 100644 Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystemFactory.cs create mode 100644 OpcComplexTypeSystemOverhaul.md create mode 100644 Tests/Opc.Ua.Client.Tests/ComplexTypes/TypeSystemClientTest.cs 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/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..cc3c10fd60 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 { diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs index de004335ac..b109ce9d19 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs @@ -27,6 +27,8 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ +using Opc.Ua.ComplexTypes; + namespace Opc.Ua.Client.ComplexTypes { /// 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/Builders/AssemblyModule.cs similarity index 98% rename from Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AssemblyModule.cs rename to Libraries/Opc.Ua.ComplexTypes/Builders/AssemblyModule.cs index 2eb7bf7792..00e38309a3 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AssemblyModule.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Builders/AssemblyModule.cs @@ -32,7 +32,7 @@ using System.Reflection; using System.Reflection.Emit; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// 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/Builders/AttributeExtensions.cs similarity index 99% rename from Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AttributeExtensions.cs rename to Libraries/Opc.Ua.ComplexTypes/Builders/AttributeExtensions.cs index ccb45547ce..1d18f4126f 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/AttributeExtensions.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Builders/AttributeExtensions.cs @@ -35,7 +35,7 @@ using System.Reflection.Emit; using System.Runtime.Serialization; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// Extensions to build attributes for the complex type builder. diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs b/Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilder.cs similarity index 99% rename from Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs rename to Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilder.cs index b0bd012c78..0287bb08a9 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeBuilder.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilder.cs @@ -32,7 +32,7 @@ using System.Reflection; using System.Reflection.Emit; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// Build an assembly with custom enum types and diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFactory.cs b/Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilderFactory.cs similarity index 99% rename from Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFactory.cs rename to Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilderFactory.cs index 4ae7deea58..1f93723732 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFactory.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilderFactory.cs @@ -33,7 +33,7 @@ using System.Linq; using System.Xml; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// Factory function for the default complex type builder diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFieldBuilder.cs b/Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeFieldBuilder.cs similarity index 99% rename from Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFieldBuilder.cs rename to Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeFieldBuilder.cs index bfe0d56d2e..a5cc206886 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeBuilder/ComplexTypeFieldBuilder.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeFieldBuilder.cs @@ -33,7 +33,7 @@ using System.Reflection.Emit; using System.Xml; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// Builder for property fields. 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.Client.ComplexTypes/Types/BaseComplexType.cs b/Libraries/Opc.Ua.ComplexTypes/Types/BaseComplexType.cs similarity index 99% rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/BaseComplexType.cs rename to Libraries/Opc.Ua.ComplexTypes/Types/BaseComplexType.cs index fff77dfdfe..69f3a0e51b 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/BaseComplexType.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Types/BaseComplexType.cs @@ -36,7 +36,7 @@ using System.Text; using System.Xml; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// The base class for all complex types. diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/ComplexTypePropertyInfo.cs b/Libraries/Opc.Ua.ComplexTypes/Types/ComplexTypePropertyInfo.cs similarity index 99% rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/ComplexTypePropertyInfo.cs rename to Libraries/Opc.Ua.ComplexTypes/Types/ComplexTypePropertyInfo.cs index d6a0c78302..71d3f479a0 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/ComplexTypePropertyInfo.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Types/ComplexTypePropertyInfo.cs @@ -32,7 +32,7 @@ using System.Reflection; using System.Runtime.Serialization; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// Complex type property info. diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/IComplexTypeProperties.cs b/Libraries/Opc.Ua.ComplexTypes/Types/IComplexTypeProperties.cs similarity index 98% rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/IComplexTypeProperties.cs rename to Libraries/Opc.Ua.ComplexTypes/Types/IComplexTypeProperties.cs index 9c2a9f1312..b49eae7c3b 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/IComplexTypeProperties.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Types/IComplexTypeProperties.cs @@ -30,7 +30,7 @@ using System; using System.Collections.Generic; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// Interface to access properties of a complex type. diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/OptionalFieldsComplexType.cs b/Libraries/Opc.Ua.ComplexTypes/Types/OptionalFieldsComplexType.cs similarity index 99% rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/OptionalFieldsComplexType.cs rename to Libraries/Opc.Ua.ComplexTypes/Types/OptionalFieldsComplexType.cs index 4da3eca528..26a2549d66 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/OptionalFieldsComplexType.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Types/OptionalFieldsComplexType.cs @@ -31,7 +31,7 @@ using System.Collections.Generic; using System.Text; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// A complex type with optional fields. diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureDefinitionAttribute.cs b/Libraries/Opc.Ua.ComplexTypes/Types/StructureDefinitionAttribute.cs similarity index 98% rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureDefinitionAttribute.cs rename to Libraries/Opc.Ua.ComplexTypes/Types/StructureDefinitionAttribute.cs index de220e6b74..876d647910 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureDefinitionAttribute.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Types/StructureDefinitionAttribute.cs @@ -29,7 +29,7 @@ using System; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// The known base complex types. diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureFieldAttribute.cs b/Libraries/Opc.Ua.ComplexTypes/Types/StructureFieldAttribute.cs similarity index 98% rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureFieldAttribute.cs rename to Libraries/Opc.Ua.ComplexTypes/Types/StructureFieldAttribute.cs index 4a98e87bcb..e985843d32 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureFieldAttribute.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Types/StructureFieldAttribute.cs @@ -29,7 +29,7 @@ using System; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// Attribute for a base complex type field definition. diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureTypeAttribute.cs b/Libraries/Opc.Ua.ComplexTypes/Types/StructureTypeAttribute.cs similarity index 98% rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureTypeAttribute.cs rename to Libraries/Opc.Ua.ComplexTypes/Types/StructureTypeAttribute.cs index 0b7c9f424d..173e6f483d 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/StructureTypeAttribute.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Types/StructureTypeAttribute.cs @@ -29,7 +29,7 @@ using System; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// Attribute for type ids of a structure definition. diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Types/UnionComplexType.cs b/Libraries/Opc.Ua.ComplexTypes/Types/UnionComplexType.cs similarity index 99% rename from Libraries/Opc.Ua.Client.ComplexTypes/Types/UnionComplexType.cs rename to Libraries/Opc.Ua.ComplexTypes/Types/UnionComplexType.cs index a59b65067d..753623dab6 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Types/UnionComplexType.cs +++ b/Libraries/Opc.Ua.ComplexTypes/Types/UnionComplexType.cs @@ -31,7 +31,7 @@ using System.Collections.Generic; using System.Text; -namespace Opc.Ua.Client.ComplexTypes +namespace Opc.Ua.ComplexTypes { /// /// Implements a union complex type. 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..34ce1c9edb --- /dev/null +++ b/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystemFactory.cs @@ -0,0 +1,103 @@ +/* ======================================================================== + * 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(). Server hosts resolve + /// this factory and call when a + /// concrete becomes available + /// (typically inside the server's CreateMasterNodeManager or + /// after server start-up) to obtain a type-loader scoped to that + /// server instance. + /// + public sealed class ServerComplexTypeSystemFactory + { + /// + /// Initializes a new instance. + /// + /// The shared telemetry context. + /// is null. + public ServerComplexTypeSystemFactory(ITelemetryContext telemetry) + { + m_telemetry = telemetry ?? throw new ArgumentNullException(nameof(telemetry)); + } + + /// + /// Creates a new bound to + /// and the host's + /// , using the AOT-friendly + /// . + /// + /// The hosted server. + /// A fresh . + /// is null. + public ServerComplexTypeSystem Create(IServerInternal server) + { + return Create(server, new DefaultComplexTypeFactory()); + } + + /// + /// Creates a new bound to + /// using a caller-supplied + /// (for example the + /// Reflection.Emit-based ComplexTypeBuilderFactory). + /// + /// 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; + } +} 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/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..84db418503 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs @@ -33,7 +33,11 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using NUnit.Framework; +<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs using Opc.Ua.Client; +======= +using Opc.Ua.ComplexTypes; +>>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/DataDictionaryTests.cs 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..3c154b560b 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs @@ -28,7 +28,11 @@ * ======================================================================*/ using NUnit.Framework; +<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs using Opc.Ua.Client; +======= +using Opc.Ua.ComplexTypes; +>>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/DefaultComplexTypesBuilderTests.cs 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..1834971eaa 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs @@ -34,7 +34,11 @@ using System.Threading; using System.Xml; using NUnit.Framework; +<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs using Opc.Ua.Client; +======= +using Opc.Ua.ComplexTypes; +>>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/DefaultComplexTypesCommon.cs 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..62cb58e46e 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs @@ -29,7 +29,11 @@ using System.Xml; using NUnit.Framework; +<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs using Opc.Ua.Client; +======= +using Opc.Ua.ComplexTypes; +>>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/DefaultEnumerationTests.cs 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..d6d1e52abb 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs @@ -30,7 +30,11 @@ using System.IO; using System.Xml; using NUnit.Framework; +<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs using Opc.Ua.Client; +======= +using Opc.Ua.ComplexTypes; +>>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/DefaultOptionSetTests.cs 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..9dc236347c 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs @@ -31,7 +31,11 @@ using BenchmarkDotNet.Attributes; using Microsoft.Extensions.Logging; using NUnit.Framework; +<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs using Opc.Ua.Client; +======= +using Opc.Ua.ComplexTypes; +>>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/NodeCacheResolverTests.cs using Opc.Ua.Client.ComplexTypes; using Opc.Ua.Client.TestFramework; diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs index bb294f48c4..a872e754a8 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs @@ -45,6 +45,8 @@ using Opc.Ua.Client.TestFramework; using Opc.Ua.Server.TestFramework; +using Opc.Ua.ComplexTypes; + namespace Opc.Ua.Client.ComplexTypes.Tests { /// diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs index d24da5edc2..3926711bdc 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs @@ -39,6 +39,8 @@ using Opc.Ua.Core.TestFramework; +using Opc.Ua.ComplexTypes; + 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..0cc7c12be0 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesTests.cs @@ -32,6 +32,8 @@ using Opc.Ua.Core.TestFramework; +using Opc.Ua.ComplexTypes; + 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..8cfa057a9b 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/EncoderTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/EncoderTests.cs @@ -37,6 +37,8 @@ using Opc.Ua.Core.TestFramework; +using Opc.Ua.ComplexTypes; + 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..9899bcb39a 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs @@ -40,6 +40,8 @@ using Opc.Ua.Core.TestFramework; +using Opc.Ua.ComplexTypes; + 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..720995e761 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs @@ -32,6 +32,8 @@ using System.Threading; using System.Threading.Tasks; +using Opc.Ua.ComplexTypes; + 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..b4156c8dd3 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs @@ -41,6 +41,8 @@ using Opc.Ua.Core.TestFramework; +using Opc.Ua.ComplexTypes; + namespace Opc.Ua.Client.ComplexTypes.Tests.Types { /// 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..eb89567e3b 100644 --- a/UA.slnx +++ b/UA.slnx @@ -39,6 +39,7 @@ + @@ -48,6 +49,7 @@ + From 66b21f869ec305205b691151be83d52356bb353b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Salih=20G=C3=B6nc=C3=BC?= Date: Thu, 28 May 2026 14:42:35 +0200 Subject: [PATCH 2/2] Second iteration - Split the AOT and legacy. --- BreakingChanges.md | 288 +++++++++++++++++ .../ComplexTypeSystemFactory.cs | 40 ++- .../Builders/AssemblyModule.cs | 2 +- .../Builders/AttributeExtensions.cs | 4 +- .../Builders/ComplexTypeBuilder.cs | 2 +- .../Builders/ComplexTypeBuilderFactory.cs | 2 +- .../Builders/ComplexTypeFieldBuilder.cs | 2 +- .../ComplexTypesEmitExtensions.cs} | 31 +- .../OpcUaComplexTypesEmitBuilderExtensions.cs | 80 +++++ .../Opc.Ua.ComplexTypes.Emit.csproj | 35 +++ .../Properties/AssemblyInfo.cs | 32 ++ .../Types/BaseComplexType.cs | 2 +- .../Types/ComplexTypePropertyInfo.cs | 2 +- .../Types/IComplexTypeProperties.cs | 2 +- .../Types/OptionalFieldsComplexType.cs | 2 +- .../Types/StructureDefinitionAttribute.cs | 2 +- .../Types/StructureFieldAttribute.cs | 2 +- .../Types/StructureTypeAttribute.cs | 2 +- .../Types/UnionComplexType.cs | 2 +- .../ServerComplexTypeSystemFactory.cs | 49 ++- OpcComplexTypesAotEmitSplit.md | 293 ++++++++++++++++++ .../DataDictionaryTests.cs | 3 - .../DefaultComplexTypesBuilderTests.cs | 3 - .../DefaultComplexTypesCommon.cs | 3 - .../DefaultEnumerationTests.cs | 3 - .../DefaultOptionSetTests.cs | 3 - .../NodeCacheResolverTests.cs | 3 - .../Opc.Ua.Client.ComplexTypes.Tests.csproj | 1 + .../TypeSystemClientTest.cs | 9 +- .../Types/ComplexTypesCommon.cs | 1 + .../Types/ComplexTypesTests.cs | 1 + .../Types/EncoderTests.cs | 1 + .../Types/JsonEncoderTests.cs | 1 + .../Types/MockResolver.cs | 1 + .../Types/MockResolverTests.cs | 7 +- UA.slnx | 1 + 36 files changed, 849 insertions(+), 68 deletions(-) create mode 100644 BreakingChanges.md rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Builders/AssemblyModule.cs (98%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Builders/AttributeExtensions.cs (99%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Builders/ComplexTypeBuilder.cs (99%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Builders/ComplexTypeBuilderFactory.cs (99%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Builders/ComplexTypeFieldBuilder.cs (99%) rename Libraries/{Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs => Opc.Ua.ComplexTypes.Emit/ComplexTypesEmitExtensions.cs} (65%) create mode 100644 Libraries/Opc.Ua.ComplexTypes.Emit/Hosting/OpcUaComplexTypesEmitBuilderExtensions.cs create mode 100644 Libraries/Opc.Ua.ComplexTypes.Emit/Opc.Ua.ComplexTypes.Emit.csproj create mode 100644 Libraries/Opc.Ua.ComplexTypes.Emit/Properties/AssemblyInfo.cs rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Types/BaseComplexType.cs (99%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Types/ComplexTypePropertyInfo.cs (99%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Types/IComplexTypeProperties.cs (98%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Types/OptionalFieldsComplexType.cs (99%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Types/StructureDefinitionAttribute.cs (98%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Types/StructureFieldAttribute.cs (98%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Types/StructureTypeAttribute.cs (98%) rename Libraries/{Opc.Ua.ComplexTypes => Opc.Ua.ComplexTypes.Emit}/Types/UnionComplexType.cs (99%) create mode 100644 OpcComplexTypesAotEmitSplit.md 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/ComplexTypeSystemFactory.cs b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystemFactory.cs index cc3c10fd60..2be55868ec 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystemFactory.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystemFactory.cs @@ -40,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)); } /// @@ -74,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.ComplexTypes/Builders/AssemblyModule.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/AssemblyModule.cs similarity index 98% rename from Libraries/Opc.Ua.ComplexTypes/Builders/AssemblyModule.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Builders/AssemblyModule.cs index 00e38309a3..5589ef2f63 100644 --- a/Libraries/Opc.Ua.ComplexTypes/Builders/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.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.ComplexTypes/Builders/AttributeExtensions.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/AttributeExtensions.cs similarity index 99% rename from Libraries/Opc.Ua.ComplexTypes/Builders/AttributeExtensions.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Builders/AttributeExtensions.cs index 1d18f4126f..dc0225b18f 100644 --- a/Libraries/Opc.Ua.ComplexTypes/Builders/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.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.ComplexTypes/Builders/ComplexTypeBuilder.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilder.cs similarity index 99% rename from Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilder.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilder.cs index 0287bb08a9..ecfe18a476 100644 --- a/Libraries/Opc.Ua.ComplexTypes/Builders/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.ComplexTypes +namespace Opc.Ua.ComplexTypes.Emit { /// /// Build an assembly with custom enum types and diff --git a/Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilderFactory.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilderFactory.cs similarity index 99% rename from Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilderFactory.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilderFactory.cs index 1f93723732..41e94bec98 100644 --- a/Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeBuilderFactory.cs +++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeBuilderFactory.cs @@ -33,7 +33,7 @@ using System.Linq; using System.Xml; -namespace Opc.Ua.ComplexTypes +namespace Opc.Ua.ComplexTypes.Emit { /// /// Factory function for the default complex type builder diff --git a/Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeFieldBuilder.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeFieldBuilder.cs similarity index 99% rename from Libraries/Opc.Ua.ComplexTypes/Builders/ComplexTypeFieldBuilder.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Builders/ComplexTypeFieldBuilder.cs index a5cc206886..baceb4a91c 100644 --- a/Libraries/Opc.Ua.ComplexTypes/Builders/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.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 65% rename from Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/ComplexTypesEmitExtensions.cs index b109ce9d19..674cc89bb2 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypesExtensions.cs +++ b/Libraries/Opc.Ua.ComplexTypes.Emit/ComplexTypesEmitExtensions.cs @@ -27,23 +27,31 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ +using Opc.Ua.Client; +using Opc.Ua.Client.ComplexTypes; using Opc.Ua.ComplexTypes; -namespace Opc.Ua.Client.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) { @@ -54,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) { @@ -66,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.ComplexTypes/Types/BaseComplexType.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/BaseComplexType.cs similarity index 99% rename from Libraries/Opc.Ua.ComplexTypes/Types/BaseComplexType.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/BaseComplexType.cs index 69f3a0e51b..099e20a1c9 100644 --- a/Libraries/Opc.Ua.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.ComplexTypes +namespace Opc.Ua.ComplexTypes.Emit { /// /// The base class for all complex types. diff --git a/Libraries/Opc.Ua.ComplexTypes/Types/ComplexTypePropertyInfo.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/ComplexTypePropertyInfo.cs similarity index 99% rename from Libraries/Opc.Ua.ComplexTypes/Types/ComplexTypePropertyInfo.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/ComplexTypePropertyInfo.cs index 71d3f479a0..17b9cae181 100644 --- a/Libraries/Opc.Ua.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.ComplexTypes +namespace Opc.Ua.ComplexTypes.Emit { /// /// Complex type property info. diff --git a/Libraries/Opc.Ua.ComplexTypes/Types/IComplexTypeProperties.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/IComplexTypeProperties.cs similarity index 98% rename from Libraries/Opc.Ua.ComplexTypes/Types/IComplexTypeProperties.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/IComplexTypeProperties.cs index b49eae7c3b..4b576dd666 100644 --- a/Libraries/Opc.Ua.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.ComplexTypes +namespace Opc.Ua.ComplexTypes.Emit { /// /// Interface to access properties of a complex type. diff --git a/Libraries/Opc.Ua.ComplexTypes/Types/OptionalFieldsComplexType.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/OptionalFieldsComplexType.cs similarity index 99% rename from Libraries/Opc.Ua.ComplexTypes/Types/OptionalFieldsComplexType.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/OptionalFieldsComplexType.cs index 26a2549d66..0c17561b81 100644 --- a/Libraries/Opc.Ua.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.ComplexTypes +namespace Opc.Ua.ComplexTypes.Emit { /// /// A complex type with optional fields. diff --git a/Libraries/Opc.Ua.ComplexTypes/Types/StructureDefinitionAttribute.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureDefinitionAttribute.cs similarity index 98% rename from Libraries/Opc.Ua.ComplexTypes/Types/StructureDefinitionAttribute.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureDefinitionAttribute.cs index 876d647910..25862210cd 100644 --- a/Libraries/Opc.Ua.ComplexTypes/Types/StructureDefinitionAttribute.cs +++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureDefinitionAttribute.cs @@ -29,7 +29,7 @@ using System; -namespace Opc.Ua.ComplexTypes +namespace Opc.Ua.ComplexTypes.Emit { /// /// The known base complex types. diff --git a/Libraries/Opc.Ua.ComplexTypes/Types/StructureFieldAttribute.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureFieldAttribute.cs similarity index 98% rename from Libraries/Opc.Ua.ComplexTypes/Types/StructureFieldAttribute.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureFieldAttribute.cs index e985843d32..ed5e168544 100644 --- a/Libraries/Opc.Ua.ComplexTypes/Types/StructureFieldAttribute.cs +++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureFieldAttribute.cs @@ -29,7 +29,7 @@ using System; -namespace Opc.Ua.ComplexTypes +namespace Opc.Ua.ComplexTypes.Emit { /// /// Attribute for a base complex type field definition. diff --git a/Libraries/Opc.Ua.ComplexTypes/Types/StructureTypeAttribute.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureTypeAttribute.cs similarity index 98% rename from Libraries/Opc.Ua.ComplexTypes/Types/StructureTypeAttribute.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureTypeAttribute.cs index 173e6f483d..1540421a6f 100644 --- a/Libraries/Opc.Ua.ComplexTypes/Types/StructureTypeAttribute.cs +++ b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/StructureTypeAttribute.cs @@ -29,7 +29,7 @@ using System; -namespace Opc.Ua.ComplexTypes +namespace Opc.Ua.ComplexTypes.Emit { /// /// Attribute for type ids of a structure definition. diff --git a/Libraries/Opc.Ua.ComplexTypes/Types/UnionComplexType.cs b/Libraries/Opc.Ua.ComplexTypes.Emit/Types/UnionComplexType.cs similarity index 99% rename from Libraries/Opc.Ua.ComplexTypes/Types/UnionComplexType.cs rename to Libraries/Opc.Ua.ComplexTypes.Emit/Types/UnionComplexType.cs index 753623dab6..20d74b2ad0 100644 --- a/Libraries/Opc.Ua.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.ComplexTypes +namespace Opc.Ua.ComplexTypes.Emit { /// /// Implements a union complex type. diff --git a/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystemFactory.cs b/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystemFactory.cs index 34ce1c9edb..05892cace8 100644 --- a/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystemFactory.cs +++ b/Libraries/Opc.Ua.Server.ComplexTypes/ServerComplexTypeSystemFactory.cs @@ -40,44 +40,68 @@ namespace Opc.Ua.Server.ComplexTypes /// /// /// Registered as a singleton by - /// IOpcUaServerBuilder.AddComplexTypes(). Server hosts resolve - /// this factory and call when a - /// concrete becomes available - /// (typically inside the server's CreateMasterNodeManager or - /// after server start-up) to obtain a type-loader scoped to that - /// server instance. + /// 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. + /// 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 - /// , using the AOT-friendly - /// . + /// . /// /// The hosted server. /// A fresh . /// is null. public ServerComplexTypeSystem Create(IServerInternal server) { - return Create(server, new DefaultComplexTypeFactory()); + return Create(server, m_complexTypeFactoryFactory()); } /// /// Creates a new bound to /// using a caller-supplied - /// (for example the - /// Reflection.Emit-based ComplexTypeBuilderFactory). + /// . 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. @@ -99,5 +123,6 @@ public ServerComplexTypeSystem Create( } private readonly ITelemetryContext m_telemetry; + private readonly Func m_complexTypeFactoryFactory; } } 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.Client.ComplexTypes.Tests/DataDictionaryTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs index 84db418503..6ba67b40c4 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs @@ -33,11 +33,8 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using NUnit.Framework; -<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/DataDictionaryTests.cs using Opc.Ua.Client; -======= using Opc.Ua.ComplexTypes; ->>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/DataDictionaryTests.cs 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 3c154b560b..d5b375fa1e 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs @@ -28,11 +28,8 @@ * ======================================================================*/ using NUnit.Framework; -<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesBuilderTests.cs using Opc.Ua.Client; -======= using Opc.Ua.ComplexTypes; ->>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/DefaultComplexTypesBuilderTests.cs 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 1834971eaa..9c893327d8 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs @@ -34,11 +34,8 @@ using System.Threading; using System.Xml; using NUnit.Framework; -<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultComplexTypesCommon.cs using Opc.Ua.Client; -======= using Opc.Ua.ComplexTypes; ->>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/DefaultComplexTypesCommon.cs 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 62cb58e46e..0c6488f0f0 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs @@ -29,11 +29,8 @@ using System.Xml; using NUnit.Framework; -<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultEnumerationTests.cs using Opc.Ua.Client; -======= using Opc.Ua.ComplexTypes; ->>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/DefaultEnumerationTests.cs 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 d6d1e52abb..9402dea11d 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs @@ -30,11 +30,8 @@ using System.IO; using System.Xml; using NUnit.Framework; -<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/DefaultOptionSetTests.cs using Opc.Ua.Client; -======= using Opc.Ua.ComplexTypes; ->>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/DefaultOptionSetTests.cs using Opc.Ua.Client.ComplexTypes; using Opc.Ua.Tests; diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs index 9dc236347c..a8b783f01c 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs @@ -31,11 +31,8 @@ using BenchmarkDotNet.Attributes; using Microsoft.Extensions.Logging; using NUnit.Framework; -<<<<<<< Updated upstream:Tests/Opc.Ua.Client.ComplexTypes.Tests/NodeCacheResolverTests.cs using Opc.Ua.Client; -======= using Opc.Ua.ComplexTypes; ->>>>>>> Stashed changes:Tests/Opc.Ua.Client.Tests/ComplexTypes/NodeCacheResolverTests.cs 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 a872e754a8..3a8ac6935d 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs @@ -46,6 +46,7 @@ using Opc.Ua.Server.TestFramework; using Opc.Ua.ComplexTypes; +using Opc.Ua.ComplexTypes.Emit; namespace Opc.Ua.Client.ComplexTypes.Tests { @@ -170,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; @@ -213,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 @@ -251,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 @@ -408,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 3926711bdc..f1323b6588 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesCommon.cs @@ -40,6 +40,7 @@ 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 0cc7c12be0..85ed81bfb1 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/ComplexTypesTests.cs @@ -33,6 +33,7 @@ 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 8cfa057a9b..f4cab1f40c 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/EncoderTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/EncoderTests.cs @@ -38,6 +38,7 @@ 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 9899bcb39a..e3cb149cdd 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs @@ -41,6 +41,7 @@ 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 720995e761..fedaaad34e 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs @@ -33,6 +33,7 @@ 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 b4156c8dd3..71168ebf2e 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs @@ -42,6 +42,7 @@ using Opc.Ua.Core.TestFramework; using Opc.Ua.ComplexTypes; +using Opc.Ua.ComplexTypes.Emit; namespace Opc.Ua.Client.ComplexTypes.Tests.Types { @@ -276,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); @@ -475,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); @@ -666,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/UA.slnx b/UA.slnx index eb89567e3b..755e203a04 100644 --- a/UA.slnx +++ b/UA.slnx @@ -40,6 +40,7 @@ +