From aed36a71cb23a08e8ec3fd05475758cb9889ff38 Mon Sep 17 00:00:00 2001 From: Loretta Date: Sun, 7 Jun 2026 07:19:47 +0200 Subject: [PATCH] Improve SignalR client deserialization and docs - Enhanced error handling in streaming methods of AcSignalRClientBase: deserialization errors are now logged and skipped instead of throwing. - SignalResponseDataMessage.GetResponseData() now throws InvalidCastException on type mismatch (was silent/null before). - Updated README to document new casting and error behavior. - Added Bash(dotnet workload *) command to settings.local.json. - Removed GeneratedCode attribute from LocationType.cs. --- .claude/settings.local.json | 3 +- .../Nav/Ekaer/Models/LocationType.cs | 2 -- .../SignalRs/AcSignalRClientBase.cs | 28 +++++++++++++++++-- .../SignalRs/IAcSignalRHubClient.cs | 6 ++-- AyCode.Services/SignalRs/README.md | 2 +- AyCode.Services/docs/SIGNALR/README.md | 6 ++-- 6 files changed, 36 insertions(+), 11 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index c90bd53..86d0e8a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -138,7 +138,8 @@ "Bash(awk '/^; Assembly listing for method AyCode.Core.Tests.TestModels.TestOrder_All_False_GeneratedWriter:WriteProperties/{found=1} found && /^; Total bytes of code/{print; exit}' jitasm_tier1.txt)", "Bash(sed -n '50108,58000p' jitasm_tier1.txt)", "Bash(awk '/^; Assembly listing for method AyCode.Core.Tests.TestModels.TestOrder_All_False_GeneratedWriter:WriteProperties/{found=1; next} found && /^; Assembly listing for method/{exit} found && /mov[[:space:]]+byte[[:space:]]+ptr/{print}' jitasm_tier1.txt)", - "Bash(git -C \"H:/Applications/Aycode/Source/AyCode.Core\" log --oneline -15 -- \"AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs\")" + "Bash(git -C \"H:/Applications/Aycode/Source/AyCode.Core\" log --oneline -15 -- \"AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs\")", + "Bash(dotnet workload *)" ] } } diff --git a/AyCode.Services/Nav/Ekaer/Models/LocationType.cs b/AyCode.Services/Nav/Ekaer/Models/LocationType.cs index 5b7bc2b..34cbde9 100644 --- a/AyCode.Services/Nav/Ekaer/Models/LocationType.cs +++ b/AyCode.Services/Nav/Ekaer/Models/LocationType.cs @@ -11,8 +11,6 @@ // xscgen --namespace http://schemas.nav.gov.hu/EKAER/1.0/ekaermanagement=AyCode.Services.Nav.Ekaer.Models --namespace http://schemas.nav.gov.hu/EKAER/1.0/common=AyCode.Services.Nav.Ekaer.Models.Common --nullable --separateFiles --output C:/Users/Fullepi/Downloads/ekaer/Generated2 C:/Users/Fullepi/Downloads/ekaer/ekaermanagement.xsd namespace AyCode.Services.Nav.Ekaer.Models { - - [System.CodeDom.Compiler.GeneratedCodeAttribute("XmlSchemaClassGenerator", "3.0.1270.0")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute("LocationType", Namespace="http://schemas.nav.gov.hu/EKAER/1.0/ekaermanagement")] diff --git a/AyCode.Services/SignalRs/AcSignalRClientBase.cs b/AyCode.Services/SignalRs/AcSignalRClientBase.cs index 9680a5f..6c2610e 100644 --- a/AyCode.Services/SignalRs/AcSignalRClientBase.cs +++ b/AyCode.Services/SignalRs/AcSignalRClientBase.cs @@ -226,7 +226,19 @@ namespace AyCode.Services.SignalRs Logger.Error(errorText); throw new Exception(errorText); } - yield return responseMessage.GetResponseData(); + + //yield return responseMessage.GetResponseData(); + TResponseData? item; + try + { + item = responseMessage.GetResponseData(); + } + catch (Exception ex) + { + Logger.Error($"Stream deser; tag {messageTag}", ex); continue; + } + + yield return item; } } } @@ -299,7 +311,19 @@ namespace AyCode.Services.SignalRs Logger.Error(errorText); throw new Exception(errorText); } - yield return responseMessage.GetResponseData(); + + //yield return responseMessage.GetResponseData(); + TResponseData? item; + try + { + item = responseMessage.GetResponseData(); + } + catch (Exception ex) + { + Logger.Error($"Stream deser; tag {messageTag}", ex); continue; + } + + yield return item; } } } diff --git a/AyCode.Services/SignalRs/IAcSignalRHubClient.cs b/AyCode.Services/SignalRs/IAcSignalRHubClient.cs index 7619acf..112bfcc 100644 --- a/AyCode.Services/SignalRs/IAcSignalRHubClient.cs +++ b/AyCode.Services/SignalRs/IAcSignalRHubClient.cs @@ -183,8 +183,10 @@ public sealed class SignalResponseDataMessage : ISignalResponseMessage /// public T? GetResponseData() { - if (RawResponseData is T typed) return typed; - return default; + var data = RawResponseData; + if (data is T typed) return typed; + + return data == null ? default : throw new InvalidCastException($"RawResponseData is NOT T! {typeof(T)} <> {data.GetType()}"); } } diff --git a/AyCode.Services/SignalRs/README.md b/AyCode.Services/SignalRs/README.md index 207c6ce..fbb61a7 100644 --- a/AyCode.Services/SignalRs/README.md +++ b/AyCode.Services/SignalRs/README.md @@ -14,7 +14,7 @@ Custom binary SignalR protocol, client infrastructure, message tagging, and seri ### Client - **`AcSignalRClientBase.cs`** — Abstract SignalR client managing `HubConnection`, request/response tracking via pooled `SignalRRequestModel`. `SendCoreAsync` builds `SignalParams` (with `IsRawBytesData` when `T == byte[]`). CRUD helpers (Post, Get, GetAll, GetAllInto). Configurable timeouts. -- **`IAcSignalRHubClient.cs`** — Client interface + `SignalResponseDataMessage` (sealed, `RawResponseData` is `object?` — typed object or byte[], `GetResponseData()` direct cast). Legacy message types (`IdMessage`, `SignalPostJsonDataMessage`) marked `[Obsolete]`. +- **`IAcSignalRHubClient.cs`** — Client interface + `SignalResponseDataMessage` (sealed, `RawResponseData` is `object?` — typed object or byte[], `GetResponseData()` safe `is T` cast — type-mismatch returns null, does **not** throw). Legacy message types (`IdMessage`, `SignalPostJsonDataMessage`) marked `[Obsolete]`. - **`IAcSignalRHubBase.cs`** — Base hub interface: `OnReceiveMessage(int messageTag, int? requestId, SignalParams signalParams, object data)`. - **`ISignalParams.cs`** — `ISignalParams` base interface + `SignalParams` (Status, DataSerializerType, Parameters `byte[]?`, SignalDataType `string?`, IsRawBytesData `bool`). Metadata travels as separate hub argument (AcBinary serialized). Parameters and data are independent — both nullable in any direction. diff --git a/AyCode.Services/docs/SIGNALR/README.md b/AyCode.Services/docs/SIGNALR/README.md index 8b17918..1217277 100644 --- a/AyCode.Services/docs/SIGNALR/README.md +++ b/AyCode.Services/docs/SIGNALR/README.md @@ -106,7 +106,7 @@ Typed access via methods (PostDataJson pattern): | Request + data | `byte[]` | typed object | client responds to server with data | | Signal | null | null/empty | ping, status change, broadcast trigger | -`SignalResponseDataMessage` is an **internal DTO** for client-side callback routing and stream wire format — constructed in-memory from `signalParams` + `data`, never serialized as envelope on wire. `RawResponseData` is `object?` (typed object or byte[]). `GetResponseData()` performs direct cast. +`SignalResponseDataMessage` is an **internal DTO** for client-side callback routing and stream wire format — constructed in-memory from `signalParams` + `data`, never serialized as envelope on wire. `RawResponseData` is `object?` (typed object or byte[]). `GetResponseData()` performs a safe `is T` cast — type-mismatch returns null, does **not** throw (so a caller-requested subtype that differs from the wire type silently yields null, not an error). ## Request/Response Flow @@ -129,8 +129,8 @@ OnReceiveMessage(tag, requestId, signalParams, object data) │ └─ { MessageTag, Status, DataSerializerType, RawResponseData = data } ├─ Matching requestId in pending dict: │ ├─ Route: null→sync wait, Action→invoke, Func→await -│ └─ GetResponseData(): direct cast (T)RawResponseData -│ Protocol already deserialized to correct type via SignalDataType +│ └─ GetResponseData(): safe `is T` cast (mismatch → null, no throw) +│ Protocol deserialized via SignalDataType = wire/server type (may ≠ caller's T) └─ No match (broadcast): └─ abstract MessageReceived(tag, signalParams, object data).Forget() ```