From b007d450d8d8e37cc44f9431eee0b6b6271cbb4f Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 8 Oct 2026 06:59:25 -0400 Subject: [PATCH] chore(build): OpcUaClient driver/browser on ITelemetryContext ctors, TreatWarningsAsErrors on remaining src projects (#621 #606) Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe --- .../ZB.MOM.WW.OtOpcUa.Client.CLI.csproj | 1 + .../OpcUaClientService.cs | 6 ++---- .../ZB.MOM.WW.OtOpcUa.Client.Shared.csproj | 1 + .../ZB.MOM.WW.OtOpcUa.Client.UI.csproj | 1 + .../OpcUaClientDriverBrowser.cs | 18 +++++++++++------- .../OpcUaClientDriver.cs | 12 ++++-------- .../SdkTelemetry.cs | 16 ++++++++++++++++ .../ZB.MOM.WW.OtOpcUa.Analyzers.csproj | 1 + ...OpcUaClientContinuationPointReleaseTests.cs | 4 ++-- .../OpcUaClientStaleSessionRaceTests.cs | 2 +- 10 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/SdkTelemetry.cs diff --git a/src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI/ZB.MOM.WW.OtOpcUa.Client.CLI.csproj b/src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI/ZB.MOM.WW.OtOpcUa.Client.CLI.csproj index fa00ef1c..03aa382a 100644 --- a/src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI/ZB.MOM.WW.OtOpcUa.Client.CLI.csproj +++ b/src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI/ZB.MOM.WW.OtOpcUa.Client.CLI.csproj @@ -4,6 +4,7 @@ Exe net10.0 enable + true enable ZB.MOM.WW.OtOpcUa.Client.CLI diff --git a/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/OpcUaClientService.cs b/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/OpcUaClientService.cs index fc9ee3b7..b64741ff 100644 --- a/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/OpcUaClientService.cs +++ b/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/OpcUaClientService.cs @@ -237,10 +237,8 @@ public sealed class OpcUaClientService : IOpcUaClientService hasChildren)); } - if (continuationPoint != null && continuationPoint.Length > 0) - (continuationPoint, references) = await _session.BrowseNextAsync(continuationPoint, ct); - else - break; + if (continuationPoint is not { Length: > 0 } pending) break; + (continuationPoint, references) = await _session.BrowseNextAsync(pending, ct); } return results; diff --git a/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/ZB.MOM.WW.OtOpcUa.Client.Shared.csproj b/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/ZB.MOM.WW.OtOpcUa.Client.Shared.csproj index 4ad02026..a2e8e203 100644 --- a/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/ZB.MOM.WW.OtOpcUa.Client.Shared.csproj +++ b/src/Client/ZB.MOM.WW.OtOpcUa.Client.Shared/ZB.MOM.WW.OtOpcUa.Client.Shared.csproj @@ -3,6 +3,7 @@ net10.0 enable + true enable ZB.MOM.WW.OtOpcUa.Client.Shared diff --git a/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ZB.MOM.WW.OtOpcUa.Client.UI.csproj b/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ZB.MOM.WW.OtOpcUa.Client.UI.csproj index 5e5a5e0e..db6dcb97 100644 --- a/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ZB.MOM.WW.OtOpcUa.Client.UI.csproj +++ b/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ZB.MOM.WW.OtOpcUa.Client.UI.csproj @@ -4,6 +4,7 @@ WinExe net10.0 enable + true enable ZB.MOM.WW.OtOpcUa.Client.UI diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/OpcUaClientDriverBrowser.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/OpcUaClientDriverBrowser.cs index 9a9d822e..af7c2ac0 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/OpcUaClientDriverBrowser.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/OpcUaClientDriverBrowser.cs @@ -124,17 +124,13 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser /// Cancellation token for the configuration build. private static async Task BuildBrowseAppConfigurationAsync(CancellationToken ct) { - // The default ctor is obsolete in favour of the ITelemetryContext overload; suppress - // locally rather than plumbing a telemetry context all the way through — the browser - // emits no per-request telemetry of its own and the SDK's internal fallback is fine - // for a transient picker session (mirrors the runtime driver's same suppression). -#pragma warning disable CS0618 - var app = new ApplicationInstance + // The ITelemetryContext overload is the non-obsolete ctor; an empty builder keeps SDK + // internals off any logging provider. + var app = new ApplicationInstance(BrowseTelemetry.Context) { ApplicationName = "OtOpcUa AdminUI Browse", ApplicationType = ApplicationType.Client, }; -#pragma warning restore CS0618 var pkiRoot = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @@ -245,3 +241,11 @@ public sealed class OpcUaClientDriverBrowser : IDriverBrowser private static string ShortName(string uri) => uri?.Substring(uri.LastIndexOf('#') + 1) ?? "(null)"; } + +internal static class BrowseTelemetry +{ + private static readonly Lazy Instance = new(() => Opc.Ua.DefaultTelemetry.Create(_ => { })); + + /// Gets the shared telemetry context. + public static Opc.Ua.ITelemetryContext Context => Instance.Value; +} diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs index 80ddbd8e..967111cf 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs @@ -294,17 +294,13 @@ public sealed class OpcUaClientDriver : IDriver, ITagDiscovery, IReadable, IWrit /// private async Task BuildApplicationConfigurationAsync(CancellationToken ct) { - // The default ctor is obsolete in favour of the ITelemetryContext overload; suppress - // locally rather than plumbing a telemetry context all the way through the driver - // surface — the driver emits no per-request telemetry of its own and the SDK's - // internal fallback is fine for a gateway use case. -#pragma warning disable CS0618 - var app = new ApplicationInstance + // The ITelemetryContext overload is the non-obsolete ctor; an empty builder keeps SDK + // internals off any logging provider (mirrors Client.Shared's ClientTelemetry). + var app = new ApplicationInstance(SdkTelemetry.Context) { ApplicationName = _options.SessionName, ApplicationType = ApplicationType.Client, }; -#pragma warning restore CS0618 var pkiRoot = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @@ -472,7 +468,7 @@ public sealed class OpcUaClientDriver : IDriver, ITagDiscovery, IReadable, IWrit endpointConfig.OperationTimeout = (int)_options.Timeout.TotalMilliseconds; var endpoint = new ConfiguredEndpoint(null, selected, endpointConfig); - var session = await new DefaultSessionFactory(telemetry: null!).CreateAsync( + var session = await new DefaultSessionFactory(SdkTelemetry.Context).CreateAsync( appConfig, endpoint, false, // updateBeforeConnect diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/SdkTelemetry.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/SdkTelemetry.cs new file mode 100644 index 00000000..02d2b8f5 --- /dev/null +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/SdkTelemetry.cs @@ -0,0 +1,16 @@ +using Opc.Ua; + +namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient; + +/// +/// The one the driver hands the SDK's non-obsolete (telemetry-taking) +/// overloads. An empty builder keeps SDK internals off any logging provider (mirrors Client.Shared's +/// ClientTelemetry). +/// +internal static class SdkTelemetry +{ + private static readonly Lazy Instance = new(() => DefaultTelemetry.Create(_ => { })); + + /// Gets the shared telemetry context. + public static ITelemetryContext Context => Instance.Value; +} diff --git a/src/Tooling/ZB.MOM.WW.OtOpcUa.Analyzers/ZB.MOM.WW.OtOpcUa.Analyzers.csproj b/src/Tooling/ZB.MOM.WW.OtOpcUa.Analyzers/ZB.MOM.WW.OtOpcUa.Analyzers.csproj index 4cb98585..0718be02 100644 --- a/src/Tooling/ZB.MOM.WW.OtOpcUa.Analyzers/ZB.MOM.WW.OtOpcUa.Analyzers.csproj +++ b/src/Tooling/ZB.MOM.WW.OtOpcUa.Analyzers/ZB.MOM.WW.OtOpcUa.Analyzers.csproj @@ -5,6 +5,7 @@ (which on .NET Framework 4.7.2 and .NET 6+ equally resolves netstandard2.0). --> netstandard2.0 enable + true latest false true diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientContinuationPointReleaseTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientContinuationPointReleaseTests.cs index 3abc109d..e4fcc323 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientContinuationPointReleaseTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientContinuationPointReleaseTests.cs @@ -37,7 +37,7 @@ public sealed class OpcUaClientContinuationPointReleaseTests private static Mock BuildCancellingSessionMock() { var mock = new Mock(MockBehavior.Loose); - mock.SetupGet(s => s.MessageContext).Returns(new ServiceMessageContext()); + mock.SetupGet(s => s.MessageContext).Returns(new ServiceMessageContext(ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.SdkTelemetry.Context)); mock.SetupGet(s => s.NamespaceUris).Returns(new NamespaceTable()); // BrowseAsync — return one Object child plus a non-empty continuation point so @@ -148,7 +148,7 @@ public sealed class OpcUaClientContinuationPointReleaseTests using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-cp-norelease"); var mock = new Mock(MockBehavior.Loose); - mock.SetupGet(s => s.MessageContext).Returns(new ServiceMessageContext()); + mock.SetupGet(s => s.MessageContext).Returns(new ServiceMessageContext(ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.SdkTelemetry.Context)); mock.SetupGet(s => s.NamespaceUris).Returns(new NamespaceTable()); // BrowseAsync — return a result with a continuation point so the loop is entered. diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientStaleSessionRaceTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientStaleSessionRaceTests.cs index 23dc9ac1..07a76e7d 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientStaleSessionRaceTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientStaleSessionRaceTests.cs @@ -53,7 +53,7 @@ public sealed class OpcUaClientStaleSessionRaceTests // MessageContext. This getter is read in the driver's synchronous prologue (before the // gate), so signalling here proves the pre-gate capture path has run. mock.SetupGet(s => s.MessageContext) - .Returns(new ServiceMessageContext()) + .Returns(new ServiceMessageContext(ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.SdkTelemetry.Context)) .Callback(() => prologueReached?.TrySetResult()); mock.SetupGet(s => s.NamespaceUris).Returns(new NamespaceTable()); mock.Setup(s => s.HistoryReadAsync(