Add logger support to grids, data sources, and helpers
- Added optional logger to TaskHelper.Forget for fire-and-forget error logging - Updated MgGridBase and data sources to accept and use logger instances - Refactored AcSignalRDataSource to log deserialization faults - Modified constructors and usages of SignalRDataSourceList/Observable for logger injection - Added CountryCode to CargoTruck and displayed in new GridCargoTruck - Introduced GridCargoTruck.razor and base class with logger integration - Updated GridCargoPartner to use new cargo truck grid as detail row - Improved code style and ensured consistent error handling throughout
This commit is contained in:
parent
101929b89e
commit
5e06f3e122
|
|
@ -1,7 +1,27 @@
|
|||
namespace AyCode.Core.Helpers
|
||||
using System.Runtime.CompilerServices;
|
||||
using AyCode.Core.Loggers;
|
||||
|
||||
namespace AyCode.Core.Helpers
|
||||
{
|
||||
public static class TaskHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional ambient logger for fire-and-forget faults. Set once at startup (after the DI
|
||||
/// container is built) in each host's entry point, e.g.
|
||||
/// <c>TaskHelper.Logger = new LoggerClient<TaskHelper>(writers);</c>.
|
||||
/// When null, faults are swallowed (legacy behaviour). A per-call <c>logger</c> argument
|
||||
/// passed to any <c>Forget</c> overload overrides this ambient instance.
|
||||
/// </summary>
|
||||
public static IAcLoggerBase? Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logs a fire-and-forget fault. Normal cancellation is filtered out by the callers,
|
||||
/// so anything reaching here is a real fault worth recording. Resolves the logger as
|
||||
/// "explicit per-call argument, else ambient <see cref="Logger"/>"; both null → silent.
|
||||
/// </summary>
|
||||
private static void LogForgetFault(Exception ex, IAcLoggerBase? logger, string caller, int line)
|
||||
=> (logger ?? Logger)?.Error($"Fire-and-forget faulted @ {caller}:{line}", ex);
|
||||
|
||||
public static bool WaitTo(Func<bool> predicate, int msTimeout = 10000, int msDelay = 5, int msFirstDelay = 0)
|
||||
=> WaitToAsync(predicate, msTimeout, msDelay, msFirstDelay).GetAwaiter().GetResult();
|
||||
|
||||
|
|
@ -48,56 +68,68 @@
|
|||
return predicate();
|
||||
}
|
||||
|
||||
public static void Forget(this Task task)
|
||||
public static void Forget(this Task task, IAcLoggerBase? logger = null, [CallerMemberName] string caller = "", [CallerLineNumber] int line = 0)
|
||||
{
|
||||
if (!task.IsCompleted || task.IsFaulted)
|
||||
_ = ForgetAwaited(task);
|
||||
if (!task.IsCompleted || task.IsFaulted) _ = ForgetAwaited(task, logger, caller, line);
|
||||
return;
|
||||
|
||||
static async Task ForgetAwaited(Task task)
|
||||
static async Task ForgetAwaited(Task task, IAcLoggerBase? logger, string caller, int line)
|
||||
{
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Swallow exception - fire and forget semantics
|
||||
// Normal cancellation — not a fault.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogForgetFault(ex, logger, caller, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Forget(this ValueTask task)
|
||||
public static void Forget(this ValueTask task, IAcLoggerBase? logger = null, [CallerMemberName] string caller = "", [CallerLineNumber] int line = 0)
|
||||
{
|
||||
if (!task.IsCompleted || task.IsFaulted)
|
||||
_ = ForgetAwaited(task);
|
||||
if (!task.IsCompleted || task.IsFaulted) _ = ForgetAwaited(task, logger, caller, line);
|
||||
return;
|
||||
|
||||
static async Task ForgetAwaited(ValueTask task)
|
||||
static async Task ForgetAwaited(ValueTask task, IAcLoggerBase? logger, string caller, int line)
|
||||
{
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Swallow exception - fire and forget semantics
|
||||
// Normal cancellation — not a fault.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogForgetFault(ex, logger, caller, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Forget<T>(this ValueTask<T> task)
|
||||
public static void Forget<T>(this ValueTask<T> task, IAcLoggerBase? logger = null, [CallerMemberName] string caller = "", [CallerLineNumber] int line = 0)
|
||||
{
|
||||
if (!task.IsCompleted || task.IsFaulted)
|
||||
_ = ForgetAwaited(task);
|
||||
if (!task.IsCompleted || task.IsFaulted) _ = ForgetAwaited(task, logger, caller, line);
|
||||
return;
|
||||
|
||||
static async Task ForgetAwaited(ValueTask<T> task)
|
||||
static async Task ForgetAwaited(ValueTask<T> task, IAcLoggerBase? logger, string caller, int line)
|
||||
{
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Swallow exception - fire and forget semantics
|
||||
// Normal cancellation — not a fault.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogForgetFault(ex, logger, caller, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ namespace AyCode.Services.Server.Tests.SignalRs.SignalRDatasources;
|
|||
public class TestOrderItemListDataSource : AcSignalRDataSource<TestOrderItem_All_True, int, List<TestOrderItem_All_True>>
|
||||
{
|
||||
public TestOrderItemListDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags crudTags)
|
||||
: base(signalRClient, crudTags) { }
|
||||
: base(signalRClient, crudTags, null) { }
|
||||
}
|
||||
|
|
@ -8,5 +8,5 @@ namespace AyCode.Services.Server.Tests.SignalRs.SignalRDatasources;
|
|||
public class TestOrderItemObservableDataSource : AcSignalRDataSource<TestOrderItem_All_True, int, AcObservableCollection<TestOrderItem_All_True>>
|
||||
{
|
||||
public TestOrderItemObservableDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags crudTags)
|
||||
: base(signalRClient, crudTags) { }
|
||||
: base(signalRClient, crudTags, null) { }
|
||||
}
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
using AyCode.Core.Enums;
|
||||
using AyCode.Core.Compression;
|
||||
using AyCode.Core.Enums;
|
||||
using AyCode.Core.Extensions;
|
||||
using AyCode.Core.Helpers;
|
||||
using AyCode.Core.Interfaces;
|
||||
using AyCode.Core.Loggers;
|
||||
using AyCode.Core.Serializers;
|
||||
using AyCode.Core.Serializers.Binaries;
|
||||
using AyCode.Services.SignalRs;
|
||||
using Castle.Core.Logging;
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using AyCode.Core.Compression;
|
||||
using AyCode.Core.Serializers;
|
||||
using AyCode.Core.Serializers.Binaries;
|
||||
|
||||
namespace AyCode.Services.Server.SignalRs
|
||||
{
|
||||
|
|
@ -218,8 +220,12 @@ namespace AyCode.Services.Server.SignalRs
|
|||
protected int FindIndexInnerListUnsafe(TId id) => InnerList.FindIndex(x => IdEquals(x.Id, id));
|
||||
protected TDataItem? FirstOrDefaultInnerListUnsafe(TId id) => InnerList.FirstOrDefault(x => IdEquals(x.Id, id));
|
||||
|
||||
public AcSignalRDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, object[]? contextIds = null)
|
||||
private IAcLoggerBase? _logger;
|
||||
|
||||
public AcSignalRDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, IAcLoggerBase? logger, object[]? contextIds = null)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
ContextIds = contextIds;
|
||||
SignalRCrudTags = signalRCrudTags;
|
||||
SignalRClient = signalRClient;
|
||||
|
|
@ -277,8 +283,7 @@ namespace AyCode.Services.Server.SignalRs
|
|||
var rawBytes = await SignalRClient.GetAllAsync<byte[]>(SignalRCrudTags.GetAllMessageTag, GetContextParams())
|
||||
?? throw new NullReferenceException("LoadDataSource; null response");
|
||||
|
||||
await LoadDataSourceFromResponseData(rawBytes, SerializerType,
|
||||
false, false, clearChangeTracking);
|
||||
await LoadDataSourceFromResponseData(rawBytes, SerializerType, false, false, clearChangeTracking);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -301,11 +306,9 @@ namespace AyCode.Services.Server.SignalRs
|
|||
{
|
||||
try
|
||||
{
|
||||
var rawBytes = await responseTask
|
||||
?? throw new NullReferenceException("LoadDataSourceAsync; null response");
|
||||
var rawBytes = await responseTask ?? throw new NullReferenceException("LoadDataSourceAsync; null response");
|
||||
|
||||
await LoadDataSourceFromResponseData(rawBytes, SerializerType,
|
||||
false, false, clearChangeTracking);
|
||||
await LoadDataSourceFromResponseData(rawBytes, SerializerType, false, false, clearChangeTracking);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -314,6 +317,39 @@ namespace AyCode.Services.Server.SignalRs
|
|||
}).Unwrap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a deserialize/populate call so a fault is logged with the concrete <typeparamref name="TDataItem"/>
|
||||
/// type before it propagates. Placed directly around the deserialize call (inside any BeginUpdate/EndUpdate
|
||||
/// scope, before EndUpdate runs) so the genuine root exception is captured even if EndUpdate's NotifyReset
|
||||
/// throws a secondary exception that would otherwise overwrite it. Rethrows — callers' behaviour is unchanged.
|
||||
/// </summary>
|
||||
private void TryDeserialize(Action deserialize)
|
||||
{
|
||||
try
|
||||
{
|
||||
deserialize();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Error($"Deserialize failed for {typeof(TDataItem).Name}", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryDeserialize(Action)"/>
|
||||
private T TryDeserialize<T>(Func<T> deserialize)
|
||||
{
|
||||
try
|
||||
{
|
||||
return deserialize();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Error($"Deserialize failed for {typeof(TDataItem).Name}", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads data source from response data.
|
||||
/// responseData is either a typed object (protocol deserialized) or byte[] (raw path).
|
||||
|
|
@ -336,7 +372,7 @@ namespace AyCode.Services.Server.SignalRs
|
|||
observable.BeginUpdate();
|
||||
try
|
||||
{
|
||||
AcBinaryDeserializer.PopulateMerge(rawBytes, 0, rawBytes.Length, InnerList);
|
||||
TryDeserialize(() => AcBinaryDeserializer.PopulateMerge(rawBytes, 0, rawBytes.Length, InnerList));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -345,7 +381,7 @@ namespace AyCode.Services.Server.SignalRs
|
|||
}
|
||||
else
|
||||
{
|
||||
AcBinaryDeserializer.Populate(rawBytes, 0, rawBytes.Length, InnerList);
|
||||
TryDeserialize(() => AcBinaryDeserializer.Populate(rawBytes, 0, rawBytes.Length, InnerList));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -364,10 +400,8 @@ namespace AyCode.Services.Server.SignalRs
|
|||
else
|
||||
{
|
||||
TIList? fromSource;
|
||||
if (serializerType == AcSerializerType.Binary)
|
||||
fromSource = AcBinaryDeserializer.Deserialize<TIList>(rawBytes, 0, rawBytes.Length);
|
||||
else
|
||||
fromSource = GzipHelper.DecompressToString(rawBytes).JsonTo<TIList>();
|
||||
if (serializerType == AcSerializerType.Binary) fromSource = TryDeserialize(() => AcBinaryDeserializer.Deserialize<TIList>(rawBytes, 0, rawBytes.Length));
|
||||
else fromSource = GzipHelper.DecompressToString(rawBytes).JsonTo<TIList>();
|
||||
|
||||
if (fromSource != null)
|
||||
{
|
||||
|
|
@ -417,17 +451,23 @@ namespace AyCode.Services.Server.SignalRs
|
|||
if (InnerList is IAcObservableCollection observable2)
|
||||
{
|
||||
observable2.BeginUpdate();
|
||||
try { AcBinaryDeserializer.PopulateMerge(reBytes, 0, reBytes.Length, InnerList); }
|
||||
finally { observable2.EndUpdate(); }
|
||||
try
|
||||
{
|
||||
TryDeserialize(() => AcBinaryDeserializer.PopulateMerge(reBytes, 0, reBytes.Length, InnerList));
|
||||
}
|
||||
finally
|
||||
{
|
||||
observable2.EndUpdate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AcBinaryDeserializer.Populate(reBytes, 0, reBytes.Length, InnerList);
|
||||
TryDeserialize(() => AcBinaryDeserializer.Populate(reBytes, 0, reBytes.Length, InnerList));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var fromSource = AcBinaryDeserializer.Deserialize<TIList>(reBytes, 0, reBytes.Length);
|
||||
var fromSource = TryDeserialize(() => AcBinaryDeserializer.Deserialize<TIList>(reBytes, 0, reBytes.Length));
|
||||
if (fromSource != null)
|
||||
{
|
||||
ClearUnsafe(clearChangeTracking);
|
||||
|
|
|
|||
Loading…
Reference in New Issue