From a9cede8ed4c5a4ec2ab704178e5036b7a9f61edb Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 22 May 2026 07:27:03 -0400 Subject: [PATCH] fix(client-ui): resolve Medium code-review finding (Client.UI-005) Call Subscriptions?.Teardown() and Alarms?.Teardown() in the Disconnected branch of OnConnectionStateChanged so server-side session drops also quiesce the DataChanged and AlarmEvent handlers. Add Reattach() methods that idempotently re-hook the handlers; call them from the Connected branch so reconnects after a server-side drop restore live updates. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ViewModels/AlarmsViewModel.cs | 10 ++++++++++ .../ViewModels/MainWindowViewModel.cs | 4 ++++ .../ViewModels/SubscriptionsViewModel.cs | 10 ++++++++++ 3 files changed, 24 insertions(+) diff --git a/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/AlarmsViewModel.cs b/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/AlarmsViewModel.cs index 42a18a9..d67fd54 100644 --- a/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/AlarmsViewModel.cs +++ b/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/AlarmsViewModel.cs @@ -215,6 +215,16 @@ public partial class AlarmsViewModel : ObservableObject ActiveAlarmCount = 0; } + /// + /// Re-hooks event handlers to the service after a server-side reconnect. + /// Safe to call when already attached (duplicate += is a no-op in .NET multicast delegates). + /// + public void Reattach() + { + _service.AlarmEvent -= OnAlarmEvent; + _service.AlarmEvent += OnAlarmEvent; + } + /// /// Unhooks event handlers from the service. /// diff --git a/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/MainWindowViewModel.cs b/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/MainWindowViewModel.cs index c757bd6..a3beb9f 100644 --- a/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/MainWindowViewModel.cs +++ b/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/MainWindowViewModel.cs @@ -166,6 +166,8 @@ public partial class MainWindowViewModel : ObservableObject { case ConnectionState.Connected: StatusMessage = $"Connected to {EndpointUrl}"; + Subscriptions?.Reattach(); + Alarms?.Reattach(); break; case ConnectionState.Reconnecting: StatusMessage = "Reconnecting..."; @@ -177,6 +179,8 @@ public partial class MainWindowViewModel : ObservableObject StatusMessage = "Disconnected"; SessionLabel = string.Empty; RedundancyInfo = null; + Subscriptions?.Teardown(); + Alarms?.Teardown(); BrowseTree?.Clear(); ReadWrite?.Clear(); Subscriptions?.Clear(); diff --git a/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/SubscriptionsViewModel.cs b/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/SubscriptionsViewModel.cs index d4ea3fa..fad17f3 100644 --- a/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/SubscriptionsViewModel.cs +++ b/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/ViewModels/SubscriptionsViewModel.cs @@ -265,6 +265,16 @@ public partial class SubscriptionsViewModel : ObservableObject SubscriptionCount = 0; } + /// + /// Re-hooks event handlers to the service after a server-side reconnect. + /// Safe to call when already attached (duplicate += is a no-op in .NET multicast delegates). + /// + public void Reattach() + { + _service.DataChanged -= OnDataChanged; + _service.DataChanged += OnDataChanged; + } + /// /// Unhooks event handlers from the service. ///