From 1a15ab4128ee8ab7af4f90d93522a198051d59c2 Mon Sep 17 00:00:00 2001 From: "jozsef.b@aycode.com" <9Rj@D}fVwBaN> Date: Tue, 4 Jun 2024 15:32:58 +0200 Subject: [PATCH 1/5] improvements, fixes --- .../Pages/User/MyServiceProviders.razor | 8 ++++- .../AddressDetailGridComponent.razor | 12 +++---- .../User/SysAdmins/AddressGridComponent.razor | 9 ++--- .../SysAdmins/ManageServiceProviders.razor | 17 +++++----- .../User/SysAdmins/ManageTransfers.razor | 25 +++++++------- .../User/SysAdmins/ProfileGridComponent.razor | 3 +- .../ServiceProviderGridComponent.razor | 5 +-- .../TransferToDriverGridComponent.razor | 3 +- .../UserProductMappingGridComponent.razor | 3 +- .../Shared/Components/Grids/TiamGrid.cs | 34 +++++++++++++++---- .../Server/Services/DevAdminSignalRhub.cs | 13 +++++-- 11 files changed, 87 insertions(+), 45 deletions(-) diff --git a/TIAMSharedUI/Pages/User/MyServiceProviders.razor b/TIAMSharedUI/Pages/User/MyServiceProviders.razor index fd04f340..87e89e70 100644 --- a/TIAMSharedUI/Pages/User/MyServiceProviders.razor +++ b/TIAMSharedUI/Pages/User/MyServiceProviders.razor @@ -68,7 +68,8 @@ PageSize="12" KeyFieldName="Id" - ValidationEnabled="false" + ValidationEnabled="false" + DetailRowDisplayMode="GridDetailRowDisplayMode.Always" CustomizeEditModel="Grid_CustomizeEditModel" EditMode="GridEditMode.EditRow" KeyboardNavigationEnabled="true"> @@ -83,6 +84,11 @@ + + @{ + @(((Company)context.DataItem).Profile.Address.AddressText) + } + diff --git a/TIAMSharedUI/Pages/User/SysAdmins/AddressDetailGridComponent.razor b/TIAMSharedUI/Pages/User/SysAdmins/AddressDetailGridComponent.razor index 2d99bc54..68cfa3c5 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/AddressDetailGridComponent.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/AddressDetailGridComponent.razor @@ -13,6 +13,7 @@ @using TIAMSharedUI.Pages.Components.EditComponents @using TIAMWebApp.Shared.Application.Services @using AyCode.Interfaces.Addresses +@using AyCode.Core @inject IServiceProviderDataService serviceProviderDataService @inject IUserDataService userDataService @inject ITransferDataService transferDataService @@ -37,15 +38,14 @@ ColumnResizeMode="GridColumnResizeMode.NextColumn" ShowFilterRow="false"> - - + + - - + + - - + @{ diff --git a/TIAMSharedUI/Pages/User/SysAdmins/AddressGridComponent.razor b/TIAMSharedUI/Pages/User/SysAdmins/AddressGridComponent.razor index 0168bab4..532c8c49 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/AddressGridComponent.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/AddressGridComponent.razor @@ -11,6 +11,7 @@ @using TIAM.Entities.Addresses @using TIAMSharedUI.Shared.Components.Grids @using TIAMSharedUI.Pages.Components.EditComponents +@using AyCode.Core @inject IServiceProviderDataService serviceProviderDataService @inject IUserDataService userDataService @inject ITransferDataService transferDataService @@ -30,11 +31,11 @@ ColumnResizeMode="GridColumnResizeMode.NextColumn" ShowFilterRow="true"> - - + + - - + + diff --git a/TIAMSharedUI/Pages/User/SysAdmins/ManageServiceProviders.razor b/TIAMSharedUI/Pages/User/SysAdmins/ManageServiceProviders.razor index ab7f7ea7..18247ea6 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/ManageServiceProviders.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/ManageServiceProviders.razor @@ -12,6 +12,7 @@ @using AyCode.Core.Extensions; @using TIAM.Entities.Addresses @using TIAMSharedUI.Shared.Components.Grids +@using AyCode.Core @layout AdminLayout @inject IEnumerable LogWriters @inject IStringLocalizer localizer @@ -92,29 +93,29 @@ KeyFieldName="Id"> - - + + - + @{ var keyField = context.Value as Guid?; - var keyItem = (Company)context.DataItem; if (keyField.IsNullOrEmpty()) { - + } else { - @keyField + @keyField.Value.ToString("N") } } - + - + + @* *@ diff --git a/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor b/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor index 842507d3..6401fa80 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor @@ -104,22 +104,22 @@ KeyFieldName="Id"> - - + + @{ - var idKeyField = ((Transfer)context.DataItem).Id as Guid?; - var editUri = $"mytransfers/{idKeyField}"; + var idKeyField = ((Transfer)context.DataItem).Id; + var editUri = $"mytransfers/{idKeyField:N}"; - @context.Value + @context.Value } - + - + @@ -139,7 +139,7 @@ TransferStatusModel keyField = Statuses.FirstOrDefault(x => x.StatusValue == Convert.ToInt16(context.Value)); string transferStatusText = keyField.StatusName; -

@transferStatusText

+ @transferStatusText }
@@ -156,16 +156,15 @@ + ContextId="((Transfer)context.DataItem).Id"> - - + + - + diff --git a/TIAMSharedUI/Pages/User/SysAdmins/ProfileGridComponent.razor b/TIAMSharedUI/Pages/User/SysAdmins/ProfileGridComponent.razor index 7fe56619..9250f1d7 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/ProfileGridComponent.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/ProfileGridComponent.razor @@ -5,6 +5,7 @@ @using TIAMWebApp.Shared.Application.Utility @using AyCode.Services.Loggers @using TIAM.Core.Loggers +@using AyCode.Core @inject IServiceProviderDataService serviceProviderDataService @inject IUserDataService userDataService @inject IEnumerable LogWriters @@ -26,7 +27,7 @@ ShowFilterRow="true"> - + diff --git a/TIAMSharedUI/Pages/User/SysAdmins/ServiceProviderGridComponent.razor b/TIAMSharedUI/Pages/User/SysAdmins/ServiceProviderGridComponent.razor index f3c1a5e1..54fd580e 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/ServiceProviderGridComponent.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/ServiceProviderGridComponent.razor @@ -8,6 +8,7 @@ @using TIAMWebApp.Shared.Application.Utility @using AyCode.Services.Loggers @using TIAM.Core.Loggers +@using AyCode.Core @inject IServiceProviderDataService ServiceProviderDataService @inject IEnumerable LogWriters @@ -27,8 +28,8 @@ ColumnResizeMode="GridColumnResizeMode.NextColumn" ShowFilterRow="true"> - - + + diff --git a/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor b/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor index 030b7790..4984cf08 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor @@ -10,6 +10,7 @@ @using TIAMWebApp.Shared.Application.Services @using AyCode.Core.Enums @using AyCode.Core.Extensions +@using AyCode.Core @inject IUserDataService UserDataService @inject IEnumerable LogWriters @inject AdminSignalRClient AdminSignalRClient; @@ -34,7 +35,7 @@ ShowFilterRow="false"> - + diff --git a/TIAMSharedUI/Pages/User/SysAdmins/UserProductMappingGridComponent.razor b/TIAMSharedUI/Pages/User/SysAdmins/UserProductMappingGridComponent.razor index 66afd837..82e3b586 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/UserProductMappingGridComponent.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/UserProductMappingGridComponent.razor @@ -8,6 +8,7 @@ @using TIAM.Core.Loggers @using AyCode.Core.Loggers @using AyCode.Services.Loggers +@using AyCode.Core @inject IServiceProviderDataService ServiceProviderDataService @inject IEnumerable LogWriters @@ -26,7 +27,7 @@ ShowFilterRow="true"> - + diff --git a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs index d159ee18..0dbdf208 100644 --- a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs +++ b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs @@ -4,10 +4,12 @@ using AyCode.Core.Enums; using AyCode.Core.Extensions; using AyCode.Core.Helpers; using AyCode.Core.Interfaces; +using AyCode.Interfaces.Entities; using AyCode.Services.SignalRs; using AyCode.Utils.Extensions; using DevExpress.Blazor; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; using TIAMWebApp.Shared.Application.Services; using TIAMWebApp.Shared.Application.Utility; @@ -16,7 +18,8 @@ namespace TIAMSharedUI.Shared.Components.Grids public class GridDataItemChangingEventArgs : GridDataItemChangedEventArgs where TDataItem : class, IId { internal GridDataItemChangingEventArgs(TiamGrid grid, TDataItem dataItem, DataChangeMode dataChangeMode) : base(grid, dataItem, dataChangeMode) - { } + { + } public bool IsCanceled { get; set; } } @@ -42,7 +45,8 @@ namespace TIAMSharedUI.Shared.Components.Grids private string _gridLogName; public TiamGrid() : base() - { } + { + } [Parameter] public LoggerClient Logger { get; set; } [Parameter] public string GridName { get; set; } @@ -56,7 +60,7 @@ namespace TIAMSharedUI.Shared.Components.Grids [Parameter] public int RemoveMessageTag { get; set; } protected new EventCallback DataItemDeleting { get; set; } - [Parameter] public EventCallback OnDataItemDeleting{ get; set; } + [Parameter] public EventCallback OnDataItemDeleting { get; set; } protected new EventCallback EditModelSaving { get; set; } [Parameter] public EventCallback OnEditModelSaving { get; set; } @@ -111,6 +115,8 @@ namespace TIAMSharedUI.Shared.Components.Grids if (firstRender) { if (_dataSource == null || _dataSource.Count == 0) RefreshDataSourceAsync().Forget(); + + //AutoFitColumnWidths(); } } @@ -124,6 +130,7 @@ namespace TIAMSharedUI.Shared.Components.Grids public Task RemoveDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, DataChangeMode.Remove); public Task RemoveDataItem(Guid id) => RemoveDataItem(id, RemoveMessageTag); + public Task RemoveDataItem(Guid id, int messageTag) { var dataItem = _dataSource.FirstOrDefault(x => x.Id == id); @@ -161,7 +168,7 @@ namespace TIAMSharedUI.Shared.Components.Grids Logger.Debug($"{_gridLogName} OnItemDeleting canceled"); return; } - + var dataItem = (e.DataItem as TDataItem)!; await RemoveDataItem(dataItem); } @@ -221,13 +228,28 @@ namespace TIAMSharedUI.Shared.Components.Grids //transfer = await devAdminSignalClient.PostDataAsync(SignalRTags.UpdateTransferAsync, transfer); } + + private void OnCustomizeElement(GridCustomizeElementEventArgs e) + { + if (e.ElementType == GridElementType.DetailCell) + { + e.Style = "padding: 0.5rem; opacity: 0.75"; + } + else if (false && e.ElementType == GridElementType.DataCell && e.Column.Name == nameof(IId.Id)) + { + e.Column.Visible = AcDomain.IsDeveloperVersion; + e.Column.ShowInColumnChooser = AcDomain.IsDeveloperVersion; + } + } + protected override Task SetParametersAsyncCore(ParameterView parameters) { if (!IsFirstInitializeParameters) { base.DataItemDeleting = EventCallback.Factory.Create(this, OnItemDeleting); base.EditModelSaving = EventCallback.Factory.Create(this, OnItemSaving); - + CustomizeElement += OnCustomizeElement; + //ShowFilterRow = true; //PageSize = 4; //ShowGroupPanel = true; @@ -235,7 +257,6 @@ namespace TIAMSharedUI.Shared.Components.Grids TextWrapEnabled = false; - //var columns = GetColumns(); //var dataColumns = GetDataColumns(); //var idColumn = dataColumns.FirstOrDefault(x => x.FieldName == nameof(IId.Id)); @@ -251,6 +272,7 @@ namespace TIAMSharedUI.Shared.Components.Grids return base.SetParametersAsyncCore(parameters); } + protected override void OnParametersSet() { base.OnParametersSet(); diff --git a/TIAMWebApp/Server/Services/DevAdminSignalRhub.cs b/TIAMWebApp/Server/Services/DevAdminSignalRhub.cs index 07d738f7..be9b565f 100644 --- a/TIAMWebApp/Server/Services/DevAdminSignalRhub.cs +++ b/TIAMWebApp/Server/Services/DevAdminSignalRhub.cs @@ -14,6 +14,10 @@ using System.Runtime.CompilerServices; using MessagePack; using TIAM.Entities.Addresses; using TIAM.Entities.Profiles; +using Microsoft.AspNetCore.Hosting; +using System.Collections.Generic; +using TIAM.Entities.Emails; +using TIAM.Services.Server; namespace TIAMWebApp.Server.Services; @@ -57,7 +61,11 @@ public class DynamicMethodCallModel where TAttribute : TagAttribute public object InstanceObject { get; init; } public ConcurrentDictionary> MethodsByMessageTag { get; init; } = new(); - public DynamicMethodCallModel(Type instanceObjectType) : this(Activator.CreateInstance(instanceObjectType)!) + + public DynamicMethodCallModel(Type instanceObjectType) : this(instanceObjectType, null!) + { } + + public DynamicMethodCallModel(Type instanceObjectType, params object[] constructorParams) : this(Activator.CreateInstance(instanceObjectType, constructorParams)!) { } public DynamicMethodCallModel(object instanceObject) @@ -75,7 +83,7 @@ public class DynamicMethodCallModel where TAttribute : TagAttribute public class DevAdminSignalRHub : Hub, IAcSignalRHubServer { - private readonly List> _dynamicMethodCallModels = new(); + private readonly List> _dynamicMethodCallModels = []; private readonly TIAM.Core.Loggers.Logger _logger; private readonly AdminDal _adminDal; @@ -93,6 +101,7 @@ public class DevAdminSignalRHub : Hub, IAcSignalRHubServe _dynamicMethodCallModels.Add(new DynamicMethodCallModel(serviceProviderApiController)); _dynamicMethodCallModels.Add(new DynamicMethodCallModel(transferDataApiController)); _dynamicMethodCallModels.Add(new DynamicMethodCallModel(messageApiController)); + //_dynamicMethodCallModels.Add(new DynamicMethodCallModel(typeof(AdminDal))); } From 4f97dcec4cee8bfcc402f2eedcdf8da50031f6e9 Mon Sep 17 00:00:00 2001 From: "jozsef.b@aycode.com" <9Rj@D}fVwBaN> Date: Wed, 5 Jun 2024 16:00:02 +0200 Subject: [PATCH 2/5] Add SignalRDataSource, SignalRDataSourceAsync --- TIAM.Services/SignalRTags.cs | 2 + .../Shared/Components/Grids/TiamGrid.cs | 16 +- .../Shared/Services/AcSignalRClientBase.cs | 7 +- .../Shared/Utility/SignalRDataSource.cs | 380 ++++++++++++++++++ .../Shared/Utility/SignalRDataSourceAsync.cs | 117 ++++++ 5 files changed, 513 insertions(+), 9 deletions(-) create mode 100644 TIAMWebApp/Shared/Utility/SignalRDataSource.cs create mode 100644 TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs diff --git a/TIAM.Services/SignalRTags.cs b/TIAM.Services/SignalRTags.cs index 398b13a0..684bfbd6 100644 --- a/TIAM.Services/SignalRTags.cs +++ b/TIAM.Services/SignalRTags.cs @@ -4,6 +4,8 @@ namespace TIAM.Services; public class SignalRTags : AcSignalRTags { + public const int None = 0; + public const int GetTransfer = 3; public const int GetTransfers = 4; public const int GetTransfersByContextId = 5; diff --git a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs index 0dbdf208..356e92f0 100644 --- a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs +++ b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs @@ -194,10 +194,10 @@ namespace TIAMSharedUI.Shared.Components.Grids protected virtual async Task PostDataToServerAsync(TDataItem dataItem, int messageTag, DataChangeMode dataChangeMode) { - var eventArgs = new GridDataItemChangingEventArgs(this, dataItem, dataChangeMode); - await OnDataItemChanging.InvokeAsync(eventArgs); + var changingEventArgs = new GridDataItemChangingEventArgs(this, dataItem, dataChangeMode); + await OnDataItemChanging.InvokeAsync(changingEventArgs); - if (eventArgs.IsCanceled) + if (changingEventArgs.IsCanceled) { Logger.Debug($"{_gridLogName} OnDataItemChanging canceled"); return; @@ -211,17 +211,19 @@ namespace TIAMSharedUI.Shared.Components.Grids _dataSource.UpdateCollection(dataItem, dataChangeMode == DataChangeMode.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. - SignalRClient.PostDataAsync(messageTag, dataItem, async repsonse => + SignalRClient.PostDataAsync(messageTag, dataItem, async response => { - if (repsonse.Status != SignalResponseStatus.Success || repsonse.ResponseData == null) + if (response.Status != SignalResponseStatus.Success || response.ResponseData == null) { RefreshDataSourceAsync().Forget(); return; } - _dataSource.UpdateCollection(repsonse.ResponseData, dataChangeMode == DataChangeMode.Remove); + _dataSource.UpdateCollection(response.ResponseData, dataChangeMode == DataChangeMode.Remove); + + var changedEventArgs = new GridDataItemChangedEventArgs(this, response.ResponseData, dataChangeMode); + await OnDataItemChanged.InvokeAsync(changedEventArgs); - await OnDataItemChanged.InvokeAsync(eventArgs); InvokeAsync(StateHasChanged).Forget(); }).Forget(); diff --git a/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs b/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs index cefac36e..b9200d75 100644 --- a/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs +++ b/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs @@ -94,13 +94,16 @@ namespace TIAMWebApp.Shared.Application.Services public virtual Task GetByIdAsync(int messageTag, Guid id, Action> responseCallback) => SendMessageToServerAsync(messageTag, new SignalPostJsonDataMessage(new IdMessage(id)), responseCallback); - public virtual Task GetAllAsync(int messageTag) where TResponse : class - => SendMessageToServerAsync(messageTag); + public virtual Task GetAllAsync(int messageTag) where TResponseData : class + => SendMessageToServerAsync(messageTag); public virtual Task GetAllAsync(int messageTag, Action> responseCallback) => SendMessageToServerAsync(messageTag, null, responseCallback); public virtual Task GetAllAsync(int messageTag, Guid? contextId, Action> responseCallback) => SendMessageToServerAsync(messageTag, (contextId.IsNullOrEmpty() ? null : new SignalPostJsonDataMessage(new IdMessage(contextId.Value))), responseCallback); + public virtual Task GetAllAsync(int messageTag, Guid? contextId) where TResponseData : class + => SendMessageToServerAsync(messageTag, contextId.IsNullOrEmpty() ? null : new SignalPostJsonDataMessage(new IdMessage(contextId.Value)), AcDomain.NextUniqueInt32); + public virtual Task PostDataAsync(int messageTag, TPostData postData) where TPostData : class => SendMessageToServerAsync(messageTag, new SignalPostJsonDataMessage(postData), AcDomain.NextUniqueInt32); public virtual Task PostDataAsync(int messageTag, TPostData postData, Action> responseCallback) where TPostData : class diff --git a/TIAMWebApp/Shared/Utility/SignalRDataSource.cs b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs new file mode 100644 index 00000000..37780551 --- /dev/null +++ b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs @@ -0,0 +1,380 @@ +using System.Collections; +using System.Collections.ObjectModel; +using System.Diagnostics; +using AyCode.Core.Enums; +using AyCode.Core.Extensions; +using AyCode.Core.Interfaces; +using AyCode.Services.SignalRs; +using TIAM.Services; +using TIAMWebApp.Shared.Application.Services; + +namespace TIAMWebApp.Shared.Application.Utility +{ + public class ChangeTracking(DataChangeMode dataChangeMode, T newItem, T originalItem = default(T))where T: class, IId + { + public DataChangeMode DataChangeMode { get; init; } = dataChangeMode; + public T NewItem { get; init; } = newItem; + public T OriginalItem { get; init; } = originalItem; + } + + [Serializable] + [DebuggerDisplay("Count = {Count}")] + public class SignalRDataSource : IList, IList, IReadOnlyList where T: class, IId + { + protected readonly List InnerList = []; + private readonly object _syncRoot = new(); + + protected Guid? ContextId; + protected AcSignalRClientBase SignalRClient; + protected readonly SignalRCrudTags SignalRCrudTags; + + public SignalRDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, Guid? contextId = null, bool autoLoadDataSource = true) + { + ContextId = contextId; + + SignalRCrudTags = signalRCrudTags; + SignalRClient = signalRClient; + + if (autoLoadDataSource) LoadDataSource(); + } + + public bool IsSynchronized => true; + public object SyncRoot => _syncRoot; + public bool IsFixedSize => false; + + /// + /// GetAllMessageTag + /// + /// + /// + public void LoadDataSource() + { + if (SignalRCrudTags.GetAllMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.GetAllMessageTag == SignalRTags.None;"); + + lock (_syncRoot) + { + var resultList = SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + + Clear(); + InnerList.AddRange(resultList); + } + } + + /// + /// set: UpdateMessageTag + /// + /// + /// + /// + public T this[int index] + { + get + { + if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); + + lock (_syncRoot) + { + return InnerList[index]; + } + } + set + { + lock (_syncRoot) + { + Update(index, value); + } + } + } + + public int Count + { + get + { + lock (_syncRoot) return InnerList.Count; + } + } + + /// + /// AddMessageTag + /// + /// + /// + public void Add(T item) + { + lock (_syncRoot) + { + if (Contains(item)) + throw new ArgumentException($@"It already contains this Id! Id: {item.Id}", nameof(item)); + + UnsafeAdd(item); + } + } + + /// + /// AddMessageTag or UpdateMessageTag + /// + /// + /// + public T AddOrUpdate(T item) + { + lock (_syncRoot) + { + var index = IndexOf(item); + + return index > -1 ? Update(index, item) : UnsafeAdd(item); + } + } + + //public void AddRange(IEnumerable collection) + //{ + // lock (_syncRoot) + // { + + // } + //} + + private T UnsafeAdd(T item) + { + if (SignalRCrudTags.AddMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.AddMessageTag == SignalRTags.None;"); + + var result = SignalRClient.PostDataAsync(SignalRCrudTags.AddMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + InnerList.Add(result); + + return result; + } + + /// + /// AddMessageTag + /// + /// + /// + /// + /// + public void Insert(int index, T item) + { + if (SignalRCrudTags.AddMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.AddMessageTag == SignalRTags.None;"); + + lock (_syncRoot) + { + if (Contains(item)) + throw new ArgumentException($@"It already contains this Id! Id: {item.Id}", nameof(item)); + + var result = SignalRClient.PostDataAsync(SignalRCrudTags.AddMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + InnerList.Insert(index, result); + } + } + + /// + /// UpdateMessageTag + /// + /// + public T Update(T item) => Update(IndexOf(item), item); + + /// + /// UpdateMessageTag + /// + /// + /// + /// /// + /// /// + /// + /// + public T Update(int index, T item) + { + if (SignalRCrudTags.UpdateMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.UpdateMessageTag == SignalRTags.None;"); + + if (default(T) != null && item == null) throw new NullReferenceException(nameof(item)); + if (item.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(item), "Update->item.Id.IsNullOrEmpty()"); + if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); + + lock (_syncRoot) + { + if (InnerList[index].Id != item.Id) + throw new ArgumentException($@"_list[index].Id != item.Id! Id: {item.Id}", nameof(item)); + + var result = SignalRClient.PostDataAsync(SignalRCrudTags.UpdateMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + + InnerList[index] = result; + return result; + } + } + + /// + /// RemoveMessageTag + /// + /// + /// + public bool Remove(T item) + { + lock (_syncRoot) + { + var index = IndexOf(item); + + if (index < 0) return false; + + RemoveAt(index); + return true; + } + } + + /// + /// RemoveMessageTag + /// + /// + /// + /// /// + /// + public void RemoveAt(int index) + { + if (SignalRCrudTags.RemoveMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.RemoveMessageTag == SignalRTags.None;"); + + lock (_syncRoot) + { + var item = InnerList[index]; + if (item.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(item), $@"RemoveAt->item.Id.IsNullOrEmpty(); index: {index}"); + + var result = SignalRClient.PostDataAsync(SignalRCrudTags.RemoveMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + + InnerList.RemoveAt(index); + } + } + + public void Clear() + { + lock (_syncRoot) InnerList.Clear(); + } + + public int IndexOf(T item) + { + lock (_syncRoot) + return InnerList.FindIndex(x => x.Id == item.Id); + } + + public bool Contains(T item) + { + lock (_syncRoot) + return IndexOf(item) > -1; + } + + public void CopyTo(T[] array) => CopyTo(array, 0); + + public void CopyTo(T[] array, int arrayIndex) + { + lock (_syncRoot) InnerList.CopyTo(array, arrayIndex); + } + + public int BinarySearch(int index, int count, T item, IComparer? comparer) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + if (Count - index < count) + throw new ArgumentException("Invalid length"); + + lock (_syncRoot) + return InnerList.BinarySearch(index, count, item, comparer); + } + + public int BinarySearch(T item) => BinarySearch(0, Count, item, null); + public int BinarySearch(T item, IComparer? comparer) => BinarySearch(0, Count, item, comparer); + + public IEnumerator GetEnumerator() + { + lock (_syncRoot) + return InnerList.ToList().GetEnumerator(); + } + + public ReadOnlyCollection AsReadOnly() => new(this); + private static bool IsCompatibleObject(object? value) => (value is T) || (value == null && default(T) == null); + + + #region IList, ICollection + bool IList.IsReadOnly => false; + + object? IList.this[int index] + { + get => this[index]; + set + { + if (default(T) != null && value == null) throw new NullReferenceException(nameof(value)); + + try + { + this[index] = (T)value!; + } + catch (InvalidCastException) + { + throw new InvalidCastException(nameof(value)); + } + } + } + + int IList.Add(object? item) + { + if (default(T) != null && item == null) throw new NullReferenceException(nameof(item)); + + try + { + Add((T)item!); + } + catch (InvalidCastException) + { + throw new InvalidCastException(nameof(item)); + } + + return Count - 1; + } + + void IList.Clear() => Clear(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + bool IList.Contains(object? item) => IsCompatibleObject(item) && Contains((T)item!); + int IList.IndexOf(object? item) => (IsCompatibleObject(item)) ? IndexOf((T)item!) : -1; + + void IList.Insert(int index, object? item) + { + if (default(T) != null && item == null) throw new NullReferenceException(nameof(item)); + + try + { + Insert(index, (T)item!); + } + catch (InvalidCastException) + { + throw new InvalidCastException(nameof(item)); + } + } + + void IList.Remove(object? item) + { + if (IsCompatibleObject(item)) Remove((T)item!); + } + + void ICollection.Clear() => Clear(); + + void ICollection.CopyTo(Array array, int arrayIndex) + { + if ((array != null) && (array.Rank != 1)) + { + throw new ArgumentException(); + } + + try + { + //TODO: _list.ToArray() - ez nem az igazi... - J. + Array.Copy(InnerList.ToArray(), 0, array!, arrayIndex, InnerList.Count); + } + catch (ArrayTypeMismatchException) + { + throw new ArrayTypeMismatchException(); + } + } + + int ICollection.Count => Count; + int ICollection.Count => Count; + bool ICollection.IsReadOnly => false; + void IList.RemoveAt(int index) => RemoveAt(index); + int IReadOnlyCollection.Count => Count; + #endregion IList, ICollection + } +} diff --git a/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs b/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs new file mode 100644 index 00000000..f46ff930 --- /dev/null +++ b/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs @@ -0,0 +1,117 @@ +using System.Diagnostics; +using AyCode.Core.Enums; +using AyCode.Core.Helpers; +using AyCode.Core.Interfaces; +using AyCode.Services.SignalRs; +using TIAM.Services; +using TIAMWebApp.Shared.Application.Services; + +namespace TIAMWebApp.Shared.Application.Utility; + +[Serializable] +[DebuggerDisplay("Count = {Count}")] +public class SignalRDataSourceAsync : SignalRDataSource where T : class, IId +{ + public Action>? OnItemChanged; + public Action>? OnDataSourceLoaded; + + public SignalRDataSourceAsync(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, Guid? contextId = null, Action>? onDataSourceLoaded = null, bool autoLoadDataSource = false) + : base(signalRClient, signalRCrudTags, contextId, false) + { + OnDataSourceLoaded = onDataSourceLoaded; + + if (autoLoadDataSource) LoadDataSourceAsync(); + } + + public void LoadDataSourceAsync() + { + if (SignalRCrudTags.GetAllMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.GetAllMessageTag == SignalRTags.None;"); + + Monitor.Exit(SyncRoot); //Exception test - J. + + Monitor.Enter(SyncRoot); + try + { + SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId, response => + { + try + { + if (response.Status == SignalResponseStatus.Error) throw new Exception($"LoadDataSourceAsync; response.Status == SignalResponseStatus.Error"); + if (response.ResponseData == null) throw new NullReferenceException($"response.ResponseData == null"); + + Clear(); + InnerList.AddRange(response.ResponseData); + } + finally + { + Monitor.Exit(SyncRoot); + } + + OnDataSourceLoaded?.Invoke(this); + }).Forget(); + } + catch (Exception) + { + Monitor.Exit(SyncRoot); + throw; + } + } + + + //public T Add(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Add).GetAwaiter().GetResult(); + //public Task AddAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Add); + + + //public Task UpdateAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Update); + + //public Task RemoveAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Remove); + + //public Task RemoveAsync(Guid id, int messageTag) + //{ + // var item = _list.FirstOrDefault(x => x.Id == id); + + // return item == null ? Task.CompletedTask : RemoveAsync(item, messageTag); + //} + + //protected virtual Task PostDataToServerAsync(T item, int messageTag, DataChangeMode dataChangeMode) + //{ + // if (messageTag == 0) return Task.CompletedTask; + + // logger.Info($"{_listLogName} PostDataToServerAsync called; transferId " + item.Id); + + // if (item.Id.IsNullOrEmpty()) item.Id = Guid.NewGuid(); + + // _list.UpdateCollection(item, dataChangeMode == DataChangeMode.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. + + // await _signalRClient.PostDataAsync(messageTag, item, async repsonse => + // { + // if (repsonse.Status != SignalResponseStatus.Success || repsonse.ResponseData == null) + // { + // RefreshDataSourceAsync().Forget(); + // return; + // } + + // _list.UpdateCollection(repsonse.ResponseData, dataChangeMode == DataChangeMode.Remove); + + // var eventArgs = new ItemChangedEventArgs(repsonse.ResponseData, dataChangeMode); + // OnItemChanged.Invoke(eventArgs); + // }); + + // //transfer = await devAdminSignalClient.PostDataAsync(SignalRTags.UpdateTransferAsync, transfer); + + // return Task.CompletedTask; + //} + + public class ItemChangedEventArgs where T : IId + { + internal ItemChangedEventArgs(T item, DataChangeMode dataChangeMode) + { + Item = item; + DataChangeMode = dataChangeMode; + } + + public T Item { get; } + public DataChangeMode DataChangeMode { get; } + } + +} \ No newline at end of file From b454c51c7b250c880732093005c9858a5408168e Mon Sep 17 00:00:00 2001 From: Loretta Date: Fri, 7 Jun 2024 06:08:49 +0200 Subject: [PATCH 3/5] Improvements SignalRDataSource; Clone obejct; etc... --- .../User/SysAdmins/ManageTransfers.razor | 2 +- .../TransferToDriverGridComponent.razor | 2 +- .../Shared/Components/Grids/TiamGrid.cs | 24 +- .../ServiceProviderAPIController.cs | 40 +- .../Shared/Services/AcSignalRClientBase.cs | 10 +- .../Shared/Utility/SignalRDataSource.cs | 415 ++++++++++++++---- .../Shared/Utility/SignalRDataSourceAsync.cs | 26 +- .../Shared/Utility/TrackingItemHelpers.cs | 47 ++ 8 files changed, 436 insertions(+), 130 deletions(-) create mode 100644 TIAMWebApp/Shared/Utility/TrackingItemHelpers.cs diff --git a/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor b/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor index 6401fa80..8cb19deb 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor @@ -396,7 +396,7 @@ { _logger.Info("DataSourceItemChanged called"); - AppointmentModels.UpdateCollection(CreateAppointmentModel(args.DataItem), args.DataChangeMode == DataChangeMode.Remove); + AppointmentModels.UpdateCollection(CreateAppointmentModel(args.DataItem), args.TrackingState == TrackingState.Remove); } private void DataItemSaving(GridEditModelSavingEventArgs e) diff --git a/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor b/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor index 4984cf08..83b676ac 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor @@ -59,7 +59,7 @@ private void DataItemChanged(GridDataItemChangedEventArgs args) { - ParentData.TransferToDrivers.UpdateCollection(args.DataItem, args.DataChangeMode == DataChangeMode.Remove); + ParentData.TransferToDrivers.UpdateCollection(args.DataItem, args.TrackingState == TrackingState.Remove); OnTransferToDriverChanged.InvokeAsync(args.DataItem); } diff --git a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs index 356e92f0..0fc18741 100644 --- a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs +++ b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs @@ -17,7 +17,7 @@ namespace TIAMSharedUI.Shared.Components.Grids { public class GridDataItemChangingEventArgs : GridDataItemChangedEventArgs where TDataItem : class, IId { - internal GridDataItemChangingEventArgs(TiamGrid grid, TDataItem dataItem, DataChangeMode dataChangeMode) : base(grid, dataItem, dataChangeMode) + internal GridDataItemChangingEventArgs(TiamGrid grid, TDataItem dataItem, TrackingState trackingState) : base(grid, dataItem, trackingState) { } @@ -26,16 +26,16 @@ namespace TIAMSharedUI.Shared.Components.Grids public class GridDataItemChangedEventArgs where TDataItem : class, IId { - internal GridDataItemChangedEventArgs(TiamGrid grid, TDataItem dataItem, DataChangeMode dataChangeMode) + internal GridDataItemChangedEventArgs(TiamGrid grid, TDataItem dataItem, TrackingState trackingState) { Grid = grid; DataItem = dataItem; - DataChangeMode = dataChangeMode; + TrackingState = trackingState; } public TiamGrid Grid { get; } public TDataItem DataItem { get; } - public DataChangeMode DataChangeMode { get; } + public TrackingState TrackingState { get; } } public class TiamGrid : DxGrid where TDataItem : class, IId @@ -121,13 +121,13 @@ namespace TIAMSharedUI.Shared.Components.Grids } public Task AddDataItem(TDataItem dataItem) => AddDataItem(dataItem, AddMessageTag); - public Task AddDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, DataChangeMode.Add); + public Task AddDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Add); public Task UpdateDataItem(TDataItem dataItem) => UpdateDataItem(dataItem, UpdateMessageTag); - public Task UpdateDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, DataChangeMode.Update); + public Task UpdateDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Update); public Task RemoveDataItem(TDataItem dataItem) => RemoveDataItem(dataItem, RemoveMessageTag); - public Task RemoveDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, DataChangeMode.Remove); + public Task RemoveDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Remove); public Task RemoveDataItem(Guid id) => RemoveDataItem(id, RemoveMessageTag); @@ -192,9 +192,9 @@ namespace TIAMSharedUI.Shared.Components.Grids }); } - protected virtual async Task PostDataToServerAsync(TDataItem dataItem, int messageTag, DataChangeMode dataChangeMode) + protected virtual async Task PostDataToServerAsync(TDataItem dataItem, int messageTag, TrackingState trackingState) { - var changingEventArgs = new GridDataItemChangingEventArgs(this, dataItem, dataChangeMode); + var changingEventArgs = new GridDataItemChangingEventArgs(this, dataItem, trackingState); await OnDataItemChanging.InvokeAsync(changingEventArgs); if (changingEventArgs.IsCanceled) @@ -209,7 +209,7 @@ namespace TIAMSharedUI.Shared.Components.Grids if (dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); - _dataSource.UpdateCollection(dataItem, dataChangeMode == DataChangeMode.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. + _dataSource.UpdateCollection(dataItem, trackingState == TrackingState.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. SignalRClient.PostDataAsync(messageTag, dataItem, async response => { @@ -219,9 +219,9 @@ namespace TIAMSharedUI.Shared.Components.Grids return; } - _dataSource.UpdateCollection(response.ResponseData, dataChangeMode == DataChangeMode.Remove); + _dataSource.UpdateCollection(response.ResponseData, trackingState == TrackingState.Remove); - var changedEventArgs = new GridDataItemChangedEventArgs(this, response.ResponseData, dataChangeMode); + var changedEventArgs = new GridDataItemChangedEventArgs(this, response.ResponseData, trackingState); await OnDataItemChanged.InvokeAsync(changedEventArgs); InvokeAsync(StateHasChanged).Forget(); diff --git a/TIAMWebApp/Server/Controllers/ServiceProviderAPIController.cs b/TIAMWebApp/Server/Controllers/ServiceProviderAPIController.cs index bc7ee8b3..f1f8419d 100644 --- a/TIAMWebApp/Server/Controllers/ServiceProviderAPIController.cs +++ b/TIAMWebApp/Server/Controllers/ServiceProviderAPIController.cs @@ -30,9 +30,9 @@ namespace TIAMWebApp.Server.Controllers [NonAction] [ApiExplorerSettings(IgnoreApi = true)] - private async Task CompanyDataChanging(Company company, DataChangeMode dataChangeMode) + private async Task CompanyDataChanging(Company company, TrackingState trackingState) { - var logText = $"[{dataChangeMode.ToString().ToUpper()}] CompanyDataChanging called; Id: {company.Id}; OwnerId: {company.OwnerId}; Name: {company.Name}"; + var logText = $"[{trackingState.ToString().ToUpper()}] CompanyDataChanging called; Id: {company.Id}; OwnerId: {company.OwnerId}; Name: {company.Name}"; if (company.Name.IsNullOrEmpty()) { @@ -42,9 +42,9 @@ namespace TIAMWebApp.Server.Controllers _logger.Info(logText); - switch (dataChangeMode) + switch (trackingState) { - case DataChangeMode.Add: + case TrackingState.Add: if (company.Id.IsNullOrEmpty()) company.Id = Guid.NewGuid(); //if (company.OwnerId.IsNullOrEmpty()) company.OwnerId = Guid.Parse("540271F6-C604-4C16-8160-D5A7CAFEDF00"); //TESZT - J. @@ -54,13 +54,13 @@ namespace TIAMWebApp.Server.Controllers return await adminDal.CreateServiceProviderAsync(company); - case DataChangeMode.Update: + case TrackingState.Update: return await adminDal.UpdateCompanyAsync(company); - case DataChangeMode.Remove: + case TrackingState.Remove: return await adminDal.RemoveCompanyAsync(company); default: - throw new ArgumentOutOfRangeException(nameof(dataChangeMode), dataChangeMode, null); + throw new ArgumentOutOfRangeException(nameof(trackingState), trackingState, null); } } @@ -68,21 +68,21 @@ namespace TIAMWebApp.Server.Controllers [ApiExplorerSettings(IgnoreApi = true)] [SignalR(SignalRTags.AddCompany)] public async Task AddCompanyAsync(Company company) - => await CompanyDataChanging(company, DataChangeMode.Add) ? company.ToJson() : string.Empty; + => await CompanyDataChanging(company, TrackingState.Add) ? company.ToJson() : string.Empty; [AllowAnonymous] [HttpPost] [Route(APIUrls.UpdateServiceProviderRouteName)] [SignalR(SignalRTags.UpdateCompany)] public async Task UpdateServiceProvider(Company company) - => await CompanyDataChanging(company, DataChangeMode.Update) ? company.ToJson() : string.Empty; + => await CompanyDataChanging(company, TrackingState.Update) ? company.ToJson() : string.Empty; [AllowAnonymous] [HttpPost] [Route(APIUrls.RemoveServiceProviderRouteName)] [SignalR(SignalRTags.RemoveCompany)] public async Task RemoveServiceProvider(Company company) - => await CompanyDataChanging(company, DataChangeMode.Remove) ? company.ToJson() : string.Empty; + => await CompanyDataChanging(company, TrackingState.Remove) ? company.ToJson() : string.Empty; //15. [AllowAnonymous] @@ -220,9 +220,9 @@ namespace TIAMWebApp.Server.Controllers [NonAction] [ApiExplorerSettings(IgnoreApi = true)] - private async Task CarDataChanging(Car car, DataChangeMode dataChangeMode) + private async Task CarDataChanging(Car car, TrackingState trackingState) { - var logText = $"[{dataChangeMode.ToString().ToUpper()}] CarDataChanging called; Id: {car.Id}; OwnerId: {car.UserProductMappingId}; LicensePlate: {car.LicencePlate}"; + var logText = $"[{trackingState.ToString().ToUpper()}] CarDataChanging called; Id: {car.Id}; OwnerId: {car.UserProductMappingId}; LicensePlate: {car.LicencePlate}"; if (car.UserProductMappingId.IsNullOrEmpty() || car.LicencePlate.IsNullOrWhiteSpace()) { @@ -232,19 +232,19 @@ namespace TIAMWebApp.Server.Controllers _logger.Info(logText); - switch (dataChangeMode) + switch (trackingState) { - case DataChangeMode.Add: + case TrackingState.Add: if (car.Id.IsNullOrEmpty()) car.Id = Guid.NewGuid(); return await adminDal.AddCarAsync(car); - case DataChangeMode.Update: + case TrackingState.Update: return await adminDal.UpdateCarAsync(car); - case DataChangeMode.Remove: + case TrackingState.Remove: return await adminDal.RemoveCarAsync(car); default: - throw new ArgumentOutOfRangeException(nameof(dataChangeMode), dataChangeMode, null); + throw new ArgumentOutOfRangeException(nameof(trackingState), trackingState, null); } } @@ -255,7 +255,7 @@ namespace TIAMWebApp.Server.Controllers [EndpointSummary("Create car")] [SignalR(SignalRTags.CreateCar)] public async Task CreateCar(Car car) - => await CarDataChanging(car, DataChangeMode.Add) ? Ok(car) : BadRequest("Invalid request"); + => await CarDataChanging(car, TrackingState.Add) ? Ok(car) : BadRequest("Invalid request"); [AllowAnonymous] [HttpPost] @@ -264,7 +264,7 @@ namespace TIAMWebApp.Server.Controllers [EndpointSummary("Update car")] [SignalR(SignalRTags.UpdateCar)] public async Task UpdateCar(Car car) - => await CarDataChanging(car, DataChangeMode.Update) ? Ok(car) : BadRequest("Invalid request"); + => await CarDataChanging(car, TrackingState.Update) ? Ok(car) : BadRequest("Invalid request"); [AllowAnonymous] [HttpPost] @@ -273,7 +273,7 @@ namespace TIAMWebApp.Server.Controllers [EndpointSummary("Delete car")] [SignalR(SignalRTags.DeleteCar)] public async Task DeleteCar(Car car) - => await CarDataChanging(car, DataChangeMode.Remove) ? Ok(car) : BadRequest("Invalid request"); + => await CarDataChanging(car, TrackingState.Remove) ? Ok(car) : BadRequest("Invalid request"); [HttpPost] [Route(APIUrls.AddProductRouteName)] diff --git a/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs b/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs index b9200d75..d531c604 100644 --- a/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs +++ b/TIAMWebApp/Shared/Services/AcSignalRClientBase.cs @@ -2,6 +2,7 @@ using AyCode.Core; using AyCode.Core.Extensions; using AyCode.Core.Helpers; +using AyCode.Core.Interfaces; using AyCode.Services.Loggers; using AyCode.Services.SignalRs; using MessagePack.Resolvers; @@ -75,9 +76,6 @@ namespace TIAMWebApp.Shared.Application.Services public virtual Task SendMessageToServerAsync(int messageTag) => SendMessageToServerAsync(messageTag, null, AcDomain.NextUniqueInt32); - public virtual Task SendMessageToServerAsync(int messageTag, int requestId) - => SendMessageToServerAsync(messageTag, null, requestId); - public virtual async Task SendMessageToServerAsync(int messageTag, ISignalRMessage? message, int? requestId) { Logger.DebugConditional($"Client SendMessageToServerAsync; {nameof(messageTag)}: {messageTag}; {nameof(requestId)}: {requestId};"); @@ -89,8 +87,8 @@ namespace TIAMWebApp.Shared.Application.Services } #region CRUD - public virtual Task GetByIdAsync(int messageTag, Guid id) where TResponse : class - => SendMessageToServerAsync(messageTag, new SignalPostJsonDataMessage(new IdMessage(id)), AcDomain.NextUniqueInt32); + public virtual Task GetByIdAsync(int messageTag, Guid id) where TResponseData : class + => SendMessageToServerAsync(messageTag, new SignalPostJsonDataMessage(new IdMessage(id)), AcDomain.NextUniqueInt32); public virtual Task GetByIdAsync(int messageTag, Guid id, Action> responseCallback) => SendMessageToServerAsync(messageTag, new SignalPostJsonDataMessage(new IdMessage(id)), responseCallback); @@ -117,7 +115,7 @@ namespace TIAMWebApp.Shared.Application.Services public virtual Task SendMessageToServerAsync(int messageTag, ISignalRMessage? message) where TResponse : class => SendMessageToServerAsync(messageTag, message, AcDomain.NextUniqueInt32); - public virtual async Task SendMessageToServerAsync(int messageTag, ISignalRMessage? message, int requestId) where TResponse : class + protected virtual async Task SendMessageToServerAsync(int messageTag, ISignalRMessage? message, int requestId) where TResponse : class { Logger.DebugConditional($"Client SendMessageToServerAsync; {nameof(messageTag)}: {messageTag}; {nameof(requestId)}: {requestId};"); diff --git a/TIAMWebApp/Shared/Utility/SignalRDataSource.cs b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs index 37780551..19b2c826 100644 --- a/TIAMWebApp/Shared/Utility/SignalRDataSource.cs +++ b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs @@ -1,6 +1,10 @@ using System.Collections; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; using AyCode.Core.Enums; using AyCode.Core.Extensions; using AyCode.Core.Interfaces; @@ -10,22 +14,84 @@ using TIAMWebApp.Shared.Application.Services; namespace TIAMWebApp.Shared.Application.Utility { - public class ChangeTracking(DataChangeMode dataChangeMode, T newItem, T originalItem = default(T))where T: class, IId + public class TrackingItem(TrackingState trackingState, T currentValue, T? originalValue = null) where T : class, IId { - public DataChangeMode DataChangeMode { get; init; } = dataChangeMode; - public T NewItem { get; init; } = newItem; - public T OriginalItem { get; init; } = originalItem; + public TrackingState TrackingState { get; internal set; } = trackingState; + public T CurrentValue { get; internal set; } = currentValue; + public T? OriginalValue { get; init; } = originalValue; //originalValue == null ? null : TrackingItemHelpers.Clone(originalValue); + + internal TrackingItem UpdateItem(TrackingState trackingState, T newValue) + { + CurrentValue = newValue; + + if (TrackingState != TrackingState.Add) + TrackingState = trackingState; + + return this; + } } + + public class ChangeTracking where T : class, IId + { + private readonly List> _trackingItems = []; //TODO: Dictionary... - J. + + internal TrackingItem? AddTrackingItem(TrackingState trackingState, T newValue, T? originalValue = null) + { + if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), $@"currentValue.Id.IsNullOrEmpty()"); + + var itemIndex = _trackingItems.FindIndex(x => x.CurrentValue.Id == newValue.Id); + TrackingItem? trackingItem = null; + + if (itemIndex > -1) + { + trackingItem = _trackingItems[itemIndex]; + + if (trackingState == TrackingState.Remove && trackingItem.TrackingState == TrackingState.Add) + { + _trackingItems.RemoveAt(itemIndex); + return null; + } + + return trackingItem.UpdateItem(trackingState, newValue); + + } + + if (originalValue != null && Equals(newValue, originalValue)) + originalValue = TrackingItemHelpers.ReflectionClone(originalValue); + + trackingItem = new TrackingItem(trackingState, newValue, originalValue); + _trackingItems.Add(trackingItem); + + return trackingItem; + } + + public int Count => _trackingItems.Count; + internal void Clear() => _trackingItems.Clear(); + public List> ToList() => _trackingItems.ToList(); + + public bool TryGetTrackingItem(Guid id, [NotNullWhen(true)] out TrackingItem? trackingItem) + { + trackingItem = _trackingItems.FirstOrDefault(x => x.CurrentValue.Id == id); + return trackingItem != null; + } + + internal void Remove(TrackingItem trackingItem) => _trackingItems.Remove(trackingItem); + } + + + [Serializable] [DebuggerDisplay("Count = {Count}")] - public class SignalRDataSource : IList, IList, IReadOnlyList where T: class, IId + public class SignalRDataSource : IList, IList, IReadOnlyList where T : class, IId { - protected readonly List InnerList = []; private readonly object _syncRoot = new(); - protected Guid? ContextId; - protected AcSignalRClientBase SignalRClient; + protected readonly List InnerList = []; //TODO: Dictionary??? - J. + protected readonly ChangeTracking TrackingItems = new(); + + protected readonly Guid? ContextId; + protected readonly AcSignalRClientBase SignalRClient; protected readonly SignalRCrudTags SignalRCrudTags; public SignalRDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, Guid? contextId = null, bool autoLoadDataSource = true) @@ -35,7 +101,7 @@ namespace TIAMWebApp.Shared.Application.Utility SignalRCrudTags = signalRCrudTags; SignalRClient = signalRClient; - if (autoLoadDataSource) LoadDataSource(); + if (autoLoadDataSource) LoadDataSource(false); } public bool IsSynchronized => true; @@ -47,19 +113,38 @@ namespace TIAMWebApp.Shared.Application.Utility /// /// /// - public void LoadDataSource() + public void LoadDataSource(bool clearChangeTracking = true) { - if (SignalRCrudTags.GetAllMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.GetAllMessageTag == SignalRTags.None;"); + if (SignalRCrudTags.GetAllMessageTag == SignalRTags.None) throw new ArgumentException($"SignalRCrudTags.GetAllMessageTag == SignalRTags.None"); lock (_syncRoot) { var resultList = SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId).GetAwaiter().GetResult() ?? throw new NullReferenceException(); - Clear(); + Clear(clearChangeTracking); InnerList.AddRange(resultList); } } + public T? LoadItem(Guid id) + { + if (SignalRCrudTags.GetItemMessageTag == SignalRTags.None) throw new ArgumentException($"SignalRCrudTags.GetItemMessageTag == SignalRTags.None"); + + T? resultitem = null; + + lock (_syncRoot) + { + resultitem = SignalRClient.GetByIdAsync(SignalRCrudTags.GetItemMessageTag, id).GetAwaiter().GetResult(); + if (resultitem == null) return null; + + if (TryGetIndex(id, out var index)) InnerList[index] = resultitem; + else InnerList.Add(resultitem); + } + + return resultitem; + } + + /// /// set: UpdateMessageTag /// @@ -86,42 +171,38 @@ namespace TIAMWebApp.Shared.Application.Utility } } - public int Count - { - get - { - lock (_syncRoot) return InnerList.Count; - } - } - /// /// AddMessageTag /// - /// + /// /// - public void Add(T item) + public void Add(T newValue) { + if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"Add->newValue.Id.IsNullOrEmpty()"); + lock (_syncRoot) { - if (Contains(item)) - throw new ArgumentException($@"It already contains this Id! Id: {item.Id}", nameof(item)); + if (Contains(newValue)) + throw new ArgumentException($@"It already contains this Id! Id: {newValue.Id}", nameof(newValue)); - UnsafeAdd(item); + UnsafeAdd(newValue); } } /// /// AddMessageTag or UpdateMessageTag /// - /// + /// /// - public T AddOrUpdate(T item) + public T AddOrUpdate(T newValue) { + if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"AddOrUpdate->newValue.Id.IsNullOrEmpty()"); + lock (_syncRoot) { - var index = IndexOf(item); + var index = IndexOf(newValue); - return index > -1 ? Update(index, item) : UnsafeAdd(item); + return index > -1 ? Update(index, newValue) : UnsafeAdd(newValue); } } @@ -133,69 +214,67 @@ namespace TIAMWebApp.Shared.Application.Utility // } //} - private T UnsafeAdd(T item) + protected T UnsafeAdd(T newValue) { - if (SignalRCrudTags.AddMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.AddMessageTag == SignalRTags.None;"); + TrackingItems.AddTrackingItem(TrackingState.Add, newValue); + InnerList.Add(newValue); - var result = SignalRClient.PostDataAsync(SignalRCrudTags.AddMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); - InnerList.Add(result); - - return result; + return newValue; } /// /// AddMessageTag /// /// - /// + /// /// /// - public void Insert(int index, T item) + public void Insert(int index, T newValue) { - if (SignalRCrudTags.AddMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.AddMessageTag == SignalRTags.None;"); + if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"Insert->newValue.Id.IsNullOrEmpty()"); lock (_syncRoot) { - if (Contains(item)) - throw new ArgumentException($@"It already contains this Id! Id: {item.Id}", nameof(item)); + if (Contains(newValue)) + throw new ArgumentException($@"It already contains this Id! Id: {newValue.Id}", nameof(newValue)); - var result = SignalRClient.PostDataAsync(SignalRCrudTags.AddMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); - InnerList.Insert(index, result); + TrackingItems.AddTrackingItem(TrackingState.Add, newValue); + InnerList.Insert(index, newValue); } } /// /// UpdateMessageTag /// - /// - public T Update(T item) => Update(IndexOf(item), item); + /// + public T Update(T newItem) => Update(IndexOf(newItem), newItem); /// /// UpdateMessageTag /// /// - /// + /// /// /// /// /// /// /// - public T Update(int index, T item) + public T Update(int index, T newValue) { - if (SignalRCrudTags.UpdateMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.UpdateMessageTag == SignalRTags.None;"); - - if (default(T) != null && item == null) throw new NullReferenceException(nameof(item)); - if (item.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(item), "Update->item.Id.IsNullOrEmpty()"); + if (default(T) != null && newValue == null) throw new NullReferenceException(nameof(newValue)); + if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"Update->newValue.Id.IsNullOrEmpty()"); if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); lock (_syncRoot) { - if (InnerList[index].Id != item.Id) - throw new ArgumentException($@"_list[index].Id != item.Id! Id: {item.Id}", nameof(item)); + var currentItem = InnerList[index]; - var result = SignalRClient.PostDataAsync(SignalRCrudTags.UpdateMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); - - InnerList[index] = result; - return result; + if (currentItem.Id != newValue.Id) + throw new ArgumentException($@"currentItem.Id != item.Id! Id: {newValue.Id}", nameof(newValue)); + + TrackingItems.AddTrackingItem(TrackingState.Update, newValue, currentItem); + InnerList[index] = newValue; + + return newValue; } } @@ -217,6 +296,20 @@ namespace TIAMWebApp.Shared.Application.Utility } } + /// + /// + /// + /// + /// + /// + public bool TryRemove(Guid id, out T? item) + { + lock (_syncRoot) + { + return TryGetValue(id, out item) && Remove(item); + } + } + /// /// RemoveMessageTag /// @@ -226,34 +319,197 @@ namespace TIAMWebApp.Shared.Application.Utility /// public void RemoveAt(int index) { - if (SignalRCrudTags.RemoveMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.RemoveMessageTag == SignalRTags.None;"); - lock (_syncRoot) { - var item = InnerList[index]; - if (item.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(item), $@"RemoveAt->item.Id.IsNullOrEmpty(); index: {index}"); - - var result = SignalRClient.PostDataAsync(SignalRCrudTags.RemoveMessageTag, item).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + var currentItem = InnerList[index]; + if (currentItem.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(currentItem), $@"RemoveAt->item.Id.IsNullOrEmpty(); index: {index}"); + TrackingItems.AddTrackingItem(TrackingState.Remove, currentItem, currentItem); InnerList.RemoveAt(index); } } - public void Clear() - { - lock (_syncRoot) InnerList.Clear(); - } - - public int IndexOf(T item) + /// + /// + /// + /// + public List> GetTrackingItems() { lock (_syncRoot) - return InnerList.FindIndex(x => x.Id == item.Id); + return TrackingItems.ToList(); } - public bool Contains(T item) + public void SetTrackingStateToUpdate(T item) { - lock (_syncRoot) - return IndexOf(item) > -1; + if (TrackingItems.TryGetTrackingItem(item.Id, out var trackingItem)) + { + if (trackingItem.TrackingState != TrackingState.Add) + trackingItem.TrackingState = TrackingState.Update; + + return; + } + + TrackingItems.AddTrackingItem(TrackingState.Update, item, item); + } + + /// + /// + /// + /// + /// + /// + public bool TryGetTrackingItem(Guid id, [NotNullWhen(true)] out TrackingItem? trackingItem) + { + lock (_syncRoot) + return TrackingItems.TryGetTrackingItem(id, out trackingItem); + } + + /// + /// + /// + /// Unsaved items + public bool SaveChanges(out List> unsavedItems) + { + lock (_syncRoot) + { + foreach (var trackingItem in TrackingItems.ToList()) + { + try + { + SaveTrackingItemUnsafe(trackingItem); + } + catch + { + // ignored + } + } + + unsavedItems = TrackingItems.ToList(); + return unsavedItems.Count == 0; + } + } + + /// + /// + /// + /// + /// + /// + public bool TrySaveItem(Guid id, [NotNullWhen(true)] out T? resultItem) + { + resultItem = null; + + if (TryGetTrackingItem(id, out var trackingItem)) + resultItem = SaveTrackingItemUnsafe(trackingItem); + + return resultItem != null; + } + + public bool TrySaveItem(Guid id, TrackingState trackingState, [NotNullWhen(true)] out T? resultItem) + => TryGetValue(id, out resultItem) && TrySaveItem(resultItem, trackingState, out resultItem); + + public bool TrySaveItem(T item, TrackingState trackingState, [NotNullWhen(true)] out T? resultItem) + { + resultItem = SaveItemUnsafe(item, trackingState); + return resultItem != null; + } + + protected T? SaveTrackingItemUnsafe(TrackingItem trackingItem) + => SaveItemUnsafe(trackingItem.CurrentValue, trackingItem.TrackingState); + + protected T? SaveItemUnsafe(T item, TrackingState trackingState) + { + var messageTag = SignalRCrudTags.GetMessageTagByTrackingState(trackingState); + if (messageTag == SignalRTags.None) return null; //throw new ArgumentException($"messageTag == SignalRTags.None"); + + var result = SignalRClient.PostDataAsync(messageTag, item).GetAwaiter().GetResult(); + if (result == null) return null; //throw new NullReferenceException($"result == null"); + + if (TryGetTrackingItem(item.Id, out var trackingItem)) + TrackingItems.Remove(trackingItem); + + if (TryGetIndex(result.Id, out var index)) + InnerList[index] = result; + + return result; + } + + protected void RollbackItemUnsafe(TrackingItem trackingItem) + { + if (TryGetIndex(trackingItem.CurrentValue.Id, out var index)) + { + if (trackingItem.TrackingState == TrackingState.Add) InnerList.RemoveAt(index); + else InnerList[index] = trackingItem.OriginalValue!; + } + else if (trackingItem.TrackingState != TrackingState.Add) + InnerList.Add(trackingItem.OriginalValue!); + + TrackingItems.Remove(trackingItem); + } + + public bool TryRollbackItem(Guid id, out T? originalValue) + { + lock (_syncRoot) + { + if (TryGetTrackingItem(id, out var trackingItem)) + { + originalValue = trackingItem.OriginalValue; + + RollbackItemUnsafe(trackingItem); + return true; + } + + originalValue = null; + return false; + } + } + + public void Rollback() + { + lock (_syncRoot) + { + foreach (var trackingItem in TrackingItems.ToList()) + RollbackItemUnsafe(trackingItem); + } + } + + public int Count + { + get + { + lock (_syncRoot) return InnerList.Count; + } + } + + public void Clear() => Clear(true); + + public void Clear(bool clearChangeTracking) + { + lock (_syncRoot) + { + if (clearChangeTracking) TrackingItems.Clear(); + InnerList.Clear(); + } + } + + public int IndexOf(Guid id) + { + lock (_syncRoot) + return InnerList.FindIndex(x => x.Id == id); + } + + public int IndexOf(T item) => IndexOf(item.Id); + public bool TryGetIndex(Guid id, out int index) => (index = IndexOf(id)) > -1; + + public bool Contains(T item) => IndexOf(item) > -1; + + public bool TryGetValue(Guid id, [NotNullWhen(true)] out T? item) + { + lock (_syncRoot) + { + item = InnerList.FirstOrDefault(x => x.Id == id); + return item != null; + } } public void CopyTo(T[] array) => CopyTo(array, 0); @@ -290,6 +546,7 @@ namespace TIAMWebApp.Shared.Application.Utility #region IList, ICollection + bool IList.IsReadOnly => false; object? IList.this[int index] @@ -326,11 +583,11 @@ namespace TIAMWebApp.Shared.Application.Utility return Count - 1; } - void IList.Clear() => Clear(); + void IList.Clear() => Clear(true); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); bool IList.Contains(object? item) => IsCompatibleObject(item) && Contains((T)item!); int IList.IndexOf(object? item) => (IsCompatibleObject(item)) ? IndexOf((T)item!) : -1; - + void IList.Insert(int index, object? item) { if (default(T) != null && item == null) throw new NullReferenceException(nameof(item)); @@ -350,7 +607,7 @@ namespace TIAMWebApp.Shared.Application.Utility if (IsCompatibleObject(item)) Remove((T)item!); } - void ICollection.Clear() => Clear(); + void ICollection.Clear() => Clear(true); void ICollection.CopyTo(Array array, int arrayIndex) { @@ -361,8 +618,11 @@ namespace TIAMWebApp.Shared.Application.Utility try { - //TODO: _list.ToArray() - ez nem az igazi... - J. - Array.Copy(InnerList.ToArray(), 0, array!, arrayIndex, InnerList.Count); + lock (_syncRoot) + { + //TODO: _list.ToArray() - ez nem az igazi... - J. + Array.Copy(InnerList.ToArray(), 0, array!, arrayIndex, InnerList.Count); + } } catch (ArrayTypeMismatchException) { @@ -375,6 +635,7 @@ namespace TIAMWebApp.Shared.Application.Utility bool ICollection.IsReadOnly => false; void IList.RemoveAt(int index) => RemoveAt(index); int IReadOnlyCollection.Count => Count; + #endregion IList, ICollection } } diff --git a/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs b/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs index f46ff930..e6361fa2 100644 --- a/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs +++ b/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs @@ -23,7 +23,7 @@ public class SignalRDataSourceAsync : SignalRDataSource where T : class, I if (autoLoadDataSource) LoadDataSourceAsync(); } - public void LoadDataSourceAsync() + public void LoadDataSourceAsync(bool clearChangeTracking = true) { if (SignalRCrudTags.GetAllMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.GetAllMessageTag == SignalRTags.None;"); @@ -39,7 +39,7 @@ public class SignalRDataSourceAsync : SignalRDataSource where T : class, I if (response.Status == SignalResponseStatus.Error) throw new Exception($"LoadDataSourceAsync; response.Status == SignalResponseStatus.Error"); if (response.ResponseData == null) throw new NullReferenceException($"response.ResponseData == null"); - Clear(); + Clear(clearChangeTracking); InnerList.AddRange(response.ResponseData); } finally @@ -58,13 +58,13 @@ public class SignalRDataSourceAsync : SignalRDataSource where T : class, I } - //public T Add(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Add).GetAwaiter().GetResult(); - //public Task AddAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Add); + //public T Add(T item, int messageTag) => PostDataToServerAsync(item, messageTag, TrackingState.Add).GetAwaiter().GetResult(); + //public Task AddAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, TrackingState.Add); - //public Task UpdateAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Update); + //public Task UpdateAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, TrackingState.Update); - //public Task RemoveAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, DataChangeMode.Remove); + //public Task RemoveAsync(T item, int messageTag) => PostDataToServerAsync(item, messageTag, TrackingState.Remove); //public Task RemoveAsync(Guid id, int messageTag) //{ @@ -73,7 +73,7 @@ public class SignalRDataSourceAsync : SignalRDataSource where T : class, I // return item == null ? Task.CompletedTask : RemoveAsync(item, messageTag); //} - //protected virtual Task PostDataToServerAsync(T item, int messageTag, DataChangeMode dataChangeMode) + //protected virtual Task PostDataToServerAsync(T item, int messageTag, TrackingState trackingState) //{ // if (messageTag == 0) return Task.CompletedTask; @@ -81,7 +81,7 @@ public class SignalRDataSourceAsync : SignalRDataSource where T : class, I // if (item.Id.IsNullOrEmpty()) item.Id = Guid.NewGuid(); - // _list.UpdateCollection(item, dataChangeMode == DataChangeMode.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. + // _list.UpdateCollection(item, trackingState == TrackingState.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. // await _signalRClient.PostDataAsync(messageTag, item, async repsonse => // { @@ -91,9 +91,9 @@ public class SignalRDataSourceAsync : SignalRDataSource where T : class, I // return; // } - // _list.UpdateCollection(repsonse.ResponseData, dataChangeMode == DataChangeMode.Remove); + // _list.UpdateCollection(repsonse.ResponseData, trackingState == TrackingState.Remove); - // var eventArgs = new ItemChangedEventArgs(repsonse.ResponseData, dataChangeMode); + // var eventArgs = new ItemChangedEventArgs(repsonse.ResponseData, trackingState); // OnItemChanged.Invoke(eventArgs); // }); @@ -104,14 +104,14 @@ public class SignalRDataSourceAsync : SignalRDataSource where T : class, I public class ItemChangedEventArgs where T : IId { - internal ItemChangedEventArgs(T item, DataChangeMode dataChangeMode) + internal ItemChangedEventArgs(T item, TrackingState trackingState) { Item = item; - DataChangeMode = dataChangeMode; + TrackingState = trackingState; } public T Item { get; } - public DataChangeMode DataChangeMode { get; } + public TrackingState TrackingState { get; } } } \ No newline at end of file diff --git a/TIAMWebApp/Shared/Utility/TrackingItemHelpers.cs b/TIAMWebApp/Shared/Utility/TrackingItemHelpers.cs new file mode 100644 index 00000000..34b5eadc --- /dev/null +++ b/TIAMWebApp/Shared/Utility/TrackingItemHelpers.cs @@ -0,0 +1,47 @@ +using System.Reflection; +using System.Text.Json; +using AyCode.Core.Extensions; + +namespace TIAMWebApp.Shared.Application.Utility; + +public static class TrackingItemHelpers +{ + public static T JsonClone(T source) => source.ToJson().JsonTo()!; + + public static T ReflectionClone(T source) + { + var type = source!.GetType(); + + if (type.IsPrimitive || typeof(string) == type) + return source; + + if (type.IsArray) + { + var elementType = Type.GetType(type.FullName!.Replace("[]", string.Empty))!; + var array = (source as Array)!; + var cloned = Array.CreateInstance(elementType, array.Length); + + for (var i = 0; i < array.Length; i++) + cloned.SetValue(ReflectionClone(array.GetValue(i)), i); + + return (T)Convert.ChangeType(cloned, type); + } + + var clone = Activator.CreateInstance(type); + + while (type != null && type != typeof(object)) + { + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + { + var fieldValue = field.GetValue(source); + if (fieldValue == null) continue; + + field.SetValue(clone, ReflectionClone(fieldValue)); + } + + type = type.BaseType; + } + + return (T)clone!; + } +} \ No newline at end of file From 96ccf6d86f5748c6e11efcf0767ca3c39e19033b Mon Sep 17 00:00:00 2001 From: Loretta Date: Sun, 9 Jun 2024 11:13:55 +0200 Subject: [PATCH 4/5] SignalRDataSource, TiamGrid imrpovements, fixes... --- .../Pages/User/MyServiceProviders.razor | 11 +- .../AddressDetailGridComponent.razor | 6 +- .../User/SysAdmins/ManageTransfers.razor | 23 +- .../TransferToDriverGridComponent.razor | 6 +- .../Shared/Components/Grids/TiamGrid.cs | 163 +++++--- .../Shared/Utility/SignalRDataSource.cs | 388 +++++++++++++++--- .../Shared/Utility/SignalRDataSourceAsync.cs | 65 ++- 7 files changed, 483 insertions(+), 179 deletions(-) diff --git a/TIAMSharedUI/Pages/User/MyServiceProviders.razor b/TIAMSharedUI/Pages/User/MyServiceProviders.razor index 87e89e70..d3ecd46e 100644 --- a/TIAMSharedUI/Pages/User/MyServiceProviders.razor +++ b/TIAMSharedUI/Pages/User/MyServiceProviders.razor @@ -82,7 +82,8 @@ - + + @{ @@ -147,19 +148,13 @@ } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - await _gridCompany.StartEditRowAsync(0); - } - void Grid_CustomizeEditModel(GridCustomizeEditModelEventArgs e) { if (e.IsNew) { var newEmployee = (Company)e.EditModel; newEmployee.Name = "John"; - newEmployee.OwnerId = Guid.NewGuid(); + newEmployee.AffiliateId = Guid.NewGuid(); } } diff --git a/TIAMSharedUI/Pages/User/SysAdmins/AddressDetailGridComponent.razor b/TIAMSharedUI/Pages/User/SysAdmins/AddressDetailGridComponent.razor index 68cfa3c5..3a092984 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/AddressDetailGridComponent.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/AddressDetailGridComponent.razor @@ -26,9 +26,9 @@ DataSource="DataSource" Logger="_logger" SignalRClient="AdminSignalRClient" - OnEditModelSaving="DataItemSaving" - OnDataItemDeleting="DataItemDeleting" - OnDataItemChanged="DataItemChanged" + OnGridEditModelSaving="DataItemSaving" + OnGridItemDeleting="DataItemDeleting" + OnGridItemChanged="DataItemChanged" PageSize="5" AutoExpandAllGroupRows="true" KeyboardNavigationEnabled="KeyboardNavigationEnabled" diff --git a/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor b/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor index 8cb19deb..2a5d97ec 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/ManageTransfers.razor @@ -17,6 +17,8 @@ @using AyCode.Core.Extensions @using AyCode.Core.Consts @using AyCode.Core +@using AyCode.Core.Helpers +@using TIAM.Entities.Emails @layout AdminLayout @inject IEnumerable LogWriters @inject IStringLocalizer localizer @@ -89,10 +91,10 @@ Logger="_logger" SignalRClient="AdminSignalRClient" OnDataSourceChanged="DataSourceChanged" - OnDataItemChanging="DataSourceItemChanging" - OnDataItemChanged="DataSourceItemChanged" - OnDataItemDeleting="DataItemDeleting" - OnEditModelSaving="DataItemSaving" + OnGridItemChanging="DataSourceItemChanging" + OnGridItemChanged="DataSourceItemChanged" + OnGridItemDeleting="DataItemDeleting" + OnGridEditModelSaving="DataItemSaving" CustomizeElement="Grid_CustomizeElement" CustomizeEditModel="Grid_CustomizeEditModel" @@ -163,7 +165,18 @@ - + + + @System.Text.RegularExpressions.Regex.Replace((displayTextContext.Value as string)!, "<(.|\n)*?>", string.Empty) + + + + @{ + var value = ((EmailMessage)editTextContext.EditModel).Text; + + } + + diff --git a/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor b/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor index 83b676ac..9744e98a 100644 --- a/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor +++ b/TIAMSharedUI/Pages/User/SysAdmins/TransferToDriverGridComponent.razor @@ -21,9 +21,9 @@ DataSource="ParentData.TransferToDrivers" Logger="_logger" SignalRClient="AdminSignalRClient" - OnEditModelSaving="DataItemSaving" - OnDataItemDeleting="DataItemDeleting" - OnDataItemChanged="DataItemChanged" + OnGridEditModelSaving="DataItemSaving" + OnGridItemDeleting="DataItemDeleting" + OnGridItemChanged="DataItemChanged" PageSize="5" AutoExpandAllGroupRows="true" KeyboardNavigationEnabled="KeyboardNavigationEnabled" diff --git a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs index 0fc18741..545fddc3 100644 --- a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs +++ b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Data.Common; using AyCode.Core; using AyCode.Core.Enums; using AyCode.Core.Extensions; @@ -8,8 +9,10 @@ using AyCode.Interfaces.Entities; using AyCode.Services.SignalRs; using AyCode.Utils.Extensions; using DevExpress.Blazor; +using DevExpress.Blazor.Internal; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; +using TIAM.Services; using TIAMWebApp.Shared.Application.Services; using TIAMWebApp.Shared.Application.Utility; @@ -41,7 +44,8 @@ namespace TIAMSharedUI.Shared.Components.Grids public class TiamGrid : DxGrid where TDataItem : class, IId { protected bool IsFirstInitializeParameters; - private IList _dataSource = null!; + private SignalRDataSource _dataSource = null!; + private IList _dataSourceParam = []; private string _gridLogName; public TiamGrid() : base() @@ -60,14 +64,17 @@ namespace TIAMSharedUI.Shared.Components.Grids [Parameter] public int RemoveMessageTag { get; set; } protected new EventCallback DataItemDeleting { get; set; } - [Parameter] public EventCallback OnDataItemDeleting { get; set; } + [Parameter] public EventCallback OnGridItemDeleting { get; set; } protected new EventCallback EditModelSaving { get; set; } - [Parameter] public EventCallback OnEditModelSaving { get; set; } + [Parameter] public EventCallback OnGridEditModelSaving { get; set; } [Parameter] public EventCallback> OnDataSourceChanged { get; set; } - [Parameter] public EventCallback> OnDataItemChanging { get; set; } - [Parameter] public EventCallback> OnDataItemChanged { get; set; } + [Parameter] public EventCallback> OnGridItemChanging { get; set; } + /// + /// After server response! + /// + [Parameter] public EventCallback> OnGridItemChanged { get; set; } [Parameter] [DefaultValue(null)] @@ -86,26 +93,65 @@ namespace TIAMSharedUI.Shared.Components.Grids } set { - if (value == null) throw new ArgumentNullException(nameof(value)); + if (value == null) return; + _dataSourceParam = value; - var equals = Equals(_dataSource, value); + //bool equals; - _dataSource = value; - Data = _dataSource; + //if ((equals = Equals(_dataSource, value)) == false) + //{ + // if (value is SignalRDataSource dataSource) + // _dataSource = dataSource; + // else + // { + // var crudTags = new SignalRCrudTags(GetAllMessageTag, SignalRTags.None, AddMessageTag, UpdateMessageTag, RemoveMessageTag); + // _dataSource = new SignalRDataSource(value, SignalRClient, crudTags, ContextId); + // } + //} - if (!equals) OnDataSourceChanged.InvokeAsync(_dataSource); + //Data = _dataSource; + //if (!equals) OnDataSourceChanged.InvokeAsync(_dataSource); } } - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { if (Logger == null) throw new NullReferenceException($"[{GetType().Name}] Logger == null"); if (SignalRClient == null) + { Logger.Error($"[{GetType().Name}] SignalRClient == null"); + throw new NullReferenceException($"[{GetType().Name}] SignalRClient == null"); + } - base.OnInitialized(); + var crudTags = new SignalRCrudTags(GetAllMessageTag, SignalRTags.None, AddMessageTag, UpdateMessageTag, RemoveMessageTag); + _dataSource = new SignalRDataSource(SignalRClient, crudTags, ContextId); + + Data = _dataSource; + + _dataSource.OnDataSourceLoaded += OnDataSourceLoaded; + _dataSource.OnDataSourceItemChanged += OnDataSourceItemChanged; + + await base.OnInitializedAsync(); + } + + private Task OnDataSourceItemChanged(ItemChangedEventArgs args) + { + if (args.TrackingState is TrackingState.GetAll or TrackingState.None) return Task.CompletedTask; + + Logger.Info($"{_gridLogName} OnItemLoaded; trackingState: {args.TrackingState}"); + + var changedEventArgs = new GridDataItemChangedEventArgs(this, args.Item, args.TrackingState); + return OnGridItemChanged.InvokeAsync(changedEventArgs); + } + + private Task OnDataSourceLoaded() + { + Logger.Info($"{_gridLogName} OnDataSourceLoaded"); + + Reload(); + return OnDataSourceChanged.InvokeAsync(_dataSource); } protected override async Task OnAfterRenderAsync(bool firstRender) @@ -114,9 +160,8 @@ namespace TIAMSharedUI.Shared.Components.Grids if (firstRender) { - if (_dataSource == null || _dataSource.Count == 0) RefreshDataSourceAsync().Forget(); - - //AutoFitColumnWidths(); + if (_dataSourceParam.Count > 0) await _dataSource.LoadDataSource(_dataSourceParam); + else _dataSource.LoadDataSourceAsync(true).Forget(); } } @@ -131,21 +176,29 @@ namespace TIAMSharedUI.Shared.Components.Grids public Task RemoveDataItem(Guid id) => RemoveDataItem(id, RemoveMessageTag); - public Task RemoveDataItem(Guid id, int messageTag) + public async Task RemoveDataItem(Guid id, int messageTag) { var dataItem = _dataSource.FirstOrDefault(x => x.Id == id); + if (dataItem != null) + { + _dataSource.Remove(dataItem); + await _dataSource.SaveChanges(); + //await RemoveDataItem(dataItem); + } - return dataItem == null ? Task.CompletedTask : RemoveDataItem(dataItem); + await InvokeAsync(StateHasChanged); } private async Task OnItemSaving(GridEditModelSavingEventArgs e) { var dataItem = (e.EditModel as TDataItem)!; + if (e.IsNew && dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); + var logText = e.IsNew ? "add" : "update"; Logger.Info($"{_gridLogName} OnItemSaving {logText}; Id: {dataItem.Id}"); - await OnEditModelSaving.InvokeAsync(e); + await OnGridEditModelSaving.InvokeAsync(e); if (e.Cancel) { @@ -153,15 +206,31 @@ namespace TIAMSharedUI.Shared.Components.Grids return; } - if (e.IsNew) await AddDataItem(dataItem); - else await UpdateDataItem(dataItem); + if (!e.IsNew) _dataSource.SetTrackingStateToUpdate(dataItem); + else + { + if (dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); + _dataSource.Add(dataItem); + } + + try + { + var unsavedItems = await _dataSource.SaveChanges(); + + if (unsavedItems.Count > 0) + Logger.Error($"OnItemSaving->TrySaveChanges error! unsavedCount: {unsavedItems.Count}"); + } + catch (Exception ex) + { + Logger.Error($"{_gridLogName} OnItemSaving", ex); + } } private async Task OnItemDeleting(GridDataItemDeletingEventArgs e) { Logger.Info($"{_gridLogName} OnItemDeleting"); - await OnDataItemDeleting.InvokeAsync(e); + await OnGridItemDeleting.InvokeAsync(e); if (e.Cancel) { @@ -170,32 +239,19 @@ namespace TIAMSharedUI.Shared.Components.Grids } var dataItem = (e.DataItem as TDataItem)!; - await RemoveDataItem(dataItem); - } + _dataSource.Remove(dataItem); - public virtual Task RefreshDataSourceAsync() - { - if (GetAllMessageTag == 0) return Task.CompletedTask; - - Logger.Info($"{_gridLogName} RefreshDataSourceAsync called"); - - return SignalRClient.GetAllAsync>(GetAllMessageTag, ContextId, response => - { - if (response.Status == SignalResponseStatus.Error) - return; - - BeginUpdate(); - DataSource = response.ResponseData ?? []; - EndUpdate(); - - InvokeAsync(StateHasChanged).Forget(); - }); + var unsavedItems = await _dataSource.SaveChanges(); + if (unsavedItems.Count > 0) + Logger.Error($"OnItemDeleting->TrySaveChanges error! unsavedCount: {unsavedItems.Count}"); } protected virtual async Task PostDataToServerAsync(TDataItem dataItem, int messageTag, TrackingState trackingState) { + return; + var changingEventArgs = new GridDataItemChangingEventArgs(this, dataItem, trackingState); - await OnDataItemChanging.InvokeAsync(changingEventArgs); + await OnGridItemChanging.InvokeAsync(changingEventArgs); if (changingEventArgs.IsCanceled) { @@ -211,21 +267,21 @@ namespace TIAMSharedUI.Shared.Components.Grids _dataSource.UpdateCollection(dataItem, trackingState == TrackingState.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. - SignalRClient.PostDataAsync(messageTag, dataItem, async response => - { - if (response.Status != SignalResponseStatus.Success || response.ResponseData == null) - { - RefreshDataSourceAsync().Forget(); - return; - } + //SignalRClient.PostDataAsync(messageTag, dataItem, async response => + //{ + // if (response.Status != SignalResponseStatus.Success || response.ResponseData == null) + // { + // RefreshDataSourceAsync().Forget(); + // return; + // } - _dataSource.UpdateCollection(response.ResponseData, trackingState == TrackingState.Remove); + // _dataSource.UpdateCollection(response.ResponseData, trackingState == TrackingState.Remove); - var changedEventArgs = new GridDataItemChangedEventArgs(this, response.ResponseData, trackingState); - await OnDataItemChanged.InvokeAsync(changedEventArgs); + // var changedEventArgs = new GridDataItemChangedEventArgs(this, response.ResponseData, trackingState); + // await OnDataItemChanged.InvokeAsync(changedEventArgs); - InvokeAsync(StateHasChanged).Forget(); - }).Forget(); + // InvokeAsync(StateHasChanged).Forget(); + //}).Forget(); //transfer = await devAdminSignalClient.PostDataAsync(SignalRTags.UpdateTransferAsync, transfer); } @@ -250,6 +306,7 @@ namespace TIAMSharedUI.Shared.Components.Grids { base.DataItemDeleting = EventCallback.Factory.Create(this, OnItemDeleting); base.EditModelSaving = EventCallback.Factory.Create(this, OnItemSaving); + CustomizeElement += OnCustomizeElement; //ShowFilterRow = true; diff --git a/TIAMWebApp/Shared/Utility/SignalRDataSource.cs b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs index 19b2c826..1a6ce0d3 100644 --- a/TIAMWebApp/Shared/Utility/SignalRDataSource.cs +++ b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs @@ -7,6 +7,7 @@ using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using AyCode.Core.Enums; using AyCode.Core.Extensions; +using AyCode.Core.Helpers; using AyCode.Core.Interfaces; using AyCode.Services.SignalRs; using TIAM.Services; @@ -18,7 +19,7 @@ namespace TIAMWebApp.Shared.Application.Utility { public TrackingState TrackingState { get; internal set; } = trackingState; public T CurrentValue { get; internal set; } = currentValue; - public T? OriginalValue { get; init; } = originalValue; //originalValue == null ? null : TrackingItemHelpers.Clone(originalValue); + public T? OriginalValue { get; init; } = originalValue; internal TrackingItem UpdateItem(TrackingState trackingState, T newValue) { @@ -32,7 +33,7 @@ namespace TIAMWebApp.Shared.Application.Utility } - public class ChangeTracking where T : class, IId + public class ChangeTracking /*: IEnumerable>*/ where T : class, IId { private readonly List> _trackingItems = []; //TODO: Dictionary... - J. @@ -58,7 +59,7 @@ namespace TIAMWebApp.Shared.Application.Utility } if (originalValue != null && Equals(newValue, originalValue)) - originalValue = TrackingItemHelpers.ReflectionClone(originalValue); + originalValue = TrackingItemHelpers.JsonClone(originalValue); trackingItem = new TrackingItem(trackingState, newValue, originalValue); _trackingItems.Add(trackingItem); @@ -77,6 +78,16 @@ namespace TIAMWebApp.Shared.Application.Utility } internal void Remove(TrackingItem trackingItem) => _trackingItems.Remove(trackingItem); + + //public IEnumerator> GetEnumerator() + //{ + // return _trackingItems.GetEnumerator(); + //} + + //IEnumerator IEnumerable.GetEnumerator() + //{ + // return GetEnumerator(); + //} } @@ -91,17 +102,18 @@ namespace TIAMWebApp.Shared.Application.Utility protected readonly ChangeTracking TrackingItems = new(); protected readonly Guid? ContextId; - protected readonly AcSignalRClientBase SignalRClient; + public AcSignalRClientBase SignalRClient; protected readonly SignalRCrudTags SignalRCrudTags; - public SignalRDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, Guid? contextId = null, bool autoLoadDataSource = true) + public Func, Task>? OnDataSourceItemChanged; + public Func? OnDataSourceLoaded; + + public SignalRDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, Guid? contextId = null) { ContextId = contextId; SignalRCrudTags = signalRCrudTags; SignalRClient = signalRClient; - - if (autoLoadDataSource) LoadDataSource(false); } public bool IsSynchronized => true; @@ -113,38 +125,79 @@ namespace TIAMWebApp.Shared.Application.Utility /// /// /// - public void LoadDataSource(bool clearChangeTracking = true) + public async Task LoadDataSource(bool clearChangeTracking = true) { if (SignalRCrudTags.GetAllMessageTag == SignalRTags.None) throw new ArgumentException($"SignalRCrudTags.GetAllMessageTag == SignalRTags.None"); - lock (_syncRoot) - { - var resultList = SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId).GetAwaiter().GetResult() ?? throw new NullReferenceException(); + var resultList = (await SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId)) ?? throw new NullReferenceException(); - Clear(clearChangeTracking); - InnerList.AddRange(resultList); - } + LoadDataSource(resultList); } - public T? LoadItem(Guid id) + public Task LoadDataSourceAsync(bool clearChangeTracking = true) + { + if (SignalRCrudTags.GetAllMessageTag == SignalRTags.None) throw new ArgumentException($"SignalRCrudTags.GetAllMessageTag == SignalRTags.None"); + + return SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId, result=> + { + if (result.Status != SignalResponseStatus.Success || result.ResponseData == null) + throw new NullReferenceException($"result.Status != SignalResponseStatus.Success || result.ResponseData == null; Status: {SignalResponseStatus.Success}"); + + LoadDataSource(result.ResponseData); + }); + } + + public async Task LoadDataSource(IList fromSource, bool clearChangeTracking = true) + { + Monitor.Enter(_syncRoot); + + try + { + Clear(clearChangeTracking); + + foreach (var item in fromSource) + { + InnerList.Add(item); + + var eventArgs = new ItemChangedEventArgs(item, TrackingState.GetAll); + if (OnDataSourceItemChanged != null) await OnDataSourceItemChanged.Invoke(eventArgs); + } + } + finally + { + Monitor.Exit(_syncRoot); + } + + if (OnDataSourceLoaded != null) await OnDataSourceLoaded.Invoke(); + } + + public async Task LoadItem(Guid id) { if (SignalRCrudTags.GetItemMessageTag == SignalRTags.None) throw new ArgumentException($"SignalRCrudTags.GetItemMessageTag == SignalRTags.None"); T? resultitem = null; - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try { - resultitem = SignalRClient.GetByIdAsync(SignalRCrudTags.GetItemMessageTag, id).GetAwaiter().GetResult(); + resultitem = await SignalRClient.GetByIdAsync(SignalRCrudTags.GetItemMessageTag, id); if (resultitem == null) return null; if (TryGetIndex(id, out var index)) InnerList[index] = resultitem; else InnerList.Add(resultitem); + + var eventArgs = new ItemChangedEventArgs(resultitem, TrackingState.Get); + if (OnDataSourceItemChanged != null) await OnDataSourceItemChanged.Invoke(eventArgs); + } + finally + { + Monitor.Exit(_syncRoot); } return resultitem; } - /// /// set: UpdateMessageTag /// @@ -157,17 +210,28 @@ namespace TIAMWebApp.Shared.Application.Utility { if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); - lock (_syncRoot) + Monitor.Enter(_syncRoot); + try { return InnerList[index]; } + finally + { + Monitor.Exit(_syncRoot); + } } set { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + try { Update(index, value); } + finally + { + Monitor.Exit(_syncRoot); + } + } } @@ -180,13 +244,20 @@ namespace TIAMWebApp.Shared.Application.Utility { if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"Add->newValue.Id.IsNullOrEmpty()"); - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try { if (Contains(newValue)) throw new ArgumentException($@"It already contains this Id! Id: {newValue.Id}", nameof(newValue)); UnsafeAdd(newValue); } + finally + { + Monitor.Exit(_syncRoot); + } + } /// @@ -198,12 +269,19 @@ namespace TIAMWebApp.Shared.Application.Utility { if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"AddOrUpdate->newValue.Id.IsNullOrEmpty()"); - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try { var index = IndexOf(newValue); return index > -1 ? Update(index, newValue) : UnsafeAdd(newValue); } + finally + { + Monitor.Exit(_syncRoot); + } + } //public void AddRange(IEnumerable collection) @@ -233,7 +311,9 @@ namespace TIAMWebApp.Shared.Application.Utility { if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"Insert->newValue.Id.IsNullOrEmpty()"); - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try { if (Contains(newValue)) throw new ArgumentException($@"It already contains this Id! Id: {newValue.Id}", nameof(newValue)); @@ -241,6 +321,11 @@ namespace TIAMWebApp.Shared.Application.Utility TrackingItems.AddTrackingItem(TrackingState.Add, newValue); InnerList.Insert(index, newValue); } + finally + { + Monitor.Exit(_syncRoot); + } + } /// @@ -264,7 +349,9 @@ namespace TIAMWebApp.Shared.Application.Utility if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"Update->newValue.Id.IsNullOrEmpty()"); if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try { var currentItem = InnerList[index]; @@ -276,6 +363,11 @@ namespace TIAMWebApp.Shared.Application.Utility return newValue; } + finally + { + Monitor.Exit(_syncRoot); + } + } /// @@ -285,7 +377,9 @@ namespace TIAMWebApp.Shared.Application.Utility /// public bool Remove(T item) { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try { var index = IndexOf(item); @@ -294,6 +388,11 @@ namespace TIAMWebApp.Shared.Application.Utility RemoveAt(index); return true; } + finally + { + Monitor.Exit(_syncRoot); + } + } /// @@ -304,10 +403,17 @@ namespace TIAMWebApp.Shared.Application.Utility /// public bool TryRemove(Guid id, out T? item) { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try { return TryGetValue(id, out item) && Remove(item); } + finally + { + Monitor.Exit(_syncRoot); + } + } /// @@ -319,7 +425,9 @@ namespace TIAMWebApp.Shared.Application.Utility /// public void RemoveAt(int index) { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try { var currentItem = InnerList[index]; if (currentItem.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(currentItem), $@"RemoveAt->item.Id.IsNullOrEmpty(); index: {index}"); @@ -327,6 +435,11 @@ namespace TIAMWebApp.Shared.Application.Utility TrackingItems.AddTrackingItem(TrackingState.Remove, currentItem, currentItem); InnerList.RemoveAt(index); } + finally + { + Monitor.Exit(_syncRoot); + } + } /// @@ -335,21 +448,39 @@ namespace TIAMWebApp.Shared.Application.Utility /// public List> GetTrackingItems() { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try + { return TrackingItems.ToList(); + } + finally + { + Monitor.Exit(_syncRoot); + } } public void SetTrackingStateToUpdate(T item) { - if (TrackingItems.TryGetTrackingItem(item.Id, out var trackingItem)) + Monitor.Enter(_syncRoot); + + try { - if (trackingItem.TrackingState != TrackingState.Add) - trackingItem.TrackingState = TrackingState.Update; + if (TrackingItems.TryGetTrackingItem(item.Id, out var trackingItem)) + { + if (trackingItem.TrackingState != TrackingState.Add) + trackingItem.TrackingState = TrackingState.Update; - return; + return; + } + + if (!TryGetValue(item.Id, out var originalItem)) return; + TrackingItems.AddTrackingItem(TrackingState.Update, item, originalItem); + } + finally + { + Monitor.Exit(_syncRoot); } - - TrackingItems.AddTrackingItem(TrackingState.Update, item, item); } /// @@ -360,33 +491,49 @@ namespace TIAMWebApp.Shared.Application.Utility /// public bool TryGetTrackingItem(Guid id, [NotNullWhen(true)] out TrackingItem? trackingItem) { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try + { return TrackingItems.TryGetTrackingItem(id, out trackingItem); + } + finally + { + Monitor.Exit(_syncRoot); + } + } /// /// /// /// Unsaved items - public bool SaveChanges(out List> unsavedItems) + public async Task>> SaveChanges() { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + + try { foreach (var trackingItem in TrackingItems.ToList()) { try { - SaveTrackingItemUnsafe(trackingItem); + //throw new Exception(); + await SaveTrackingItemUnsafe(trackingItem); } - catch + catch(Exception ex) { - // ignored + TryRollbackItem(trackingItem.CurrentValue.Id, out _); } } - unsavedItems = TrackingItems.ToList(); - return unsavedItems.Count == 0; + return TrackingItems.ToList(); } + finally + { + Monitor.Exit(_syncRoot); + } + } /// @@ -395,34 +542,55 @@ namespace TIAMWebApp.Shared.Application.Utility /// /// /// - public bool TrySaveItem(Guid id, [NotNullWhen(true)] out T? resultItem) + public async Task SaveItem(Guid id) { - resultItem = null; + Monitor.Enter(_syncRoot); - if (TryGetTrackingItem(id, out var trackingItem)) - resultItem = SaveTrackingItemUnsafe(trackingItem); + try + { + T? resultItem = null; - return resultItem != null; + if (TryGetTrackingItem(id, out var trackingItem)) + resultItem = await SaveTrackingItemUnsafe(trackingItem); + + return resultItem; + } + finally + { + Monitor.Exit(_syncRoot); + } } - public bool TrySaveItem(Guid id, TrackingState trackingState, [NotNullWhen(true)] out T? resultItem) - => TryGetValue(id, out resultItem) && TrySaveItem(resultItem, trackingState, out resultItem); - - public bool TrySaveItem(T item, TrackingState trackingState, [NotNullWhen(true)] out T? resultItem) + public async Task SaveItem(Guid id, TrackingState trackingState) { - resultItem = SaveItemUnsafe(item, trackingState); - return resultItem != null; + Monitor.Enter(_syncRoot); + + try + { + T? resultItem = null; + + if (TryGetValue(id, out var item)) + resultItem = await SaveItem(item, trackingState); + + return resultItem; + } + finally + { + Monitor.Exit(_syncRoot); + } } - protected T? SaveTrackingItemUnsafe(TrackingItem trackingItem) + public Task SaveItem(T item, TrackingState trackingState) => SaveItemUnsafe(item, trackingState); + + protected Task SaveTrackingItemUnsafe(TrackingItem trackingItem) => SaveItemUnsafe(trackingItem.CurrentValue, trackingItem.TrackingState); - protected T? SaveItemUnsafe(T item, TrackingState trackingState) + protected async Task SaveItemUnsafe(T item, TrackingState trackingState) { var messageTag = SignalRCrudTags.GetMessageTagByTrackingState(trackingState); if (messageTag == SignalRTags.None) return null; //throw new ArgumentException($"messageTag == SignalRTags.None"); - var result = SignalRClient.PostDataAsync(messageTag, item).GetAwaiter().GetResult(); + var result = await SignalRClient.PostDataAsync(messageTag, item); if (result == null) return null; //throw new NullReferenceException($"result == null"); if (TryGetTrackingItem(item.Id, out var trackingItem)) @@ -431,6 +599,9 @@ namespace TIAMWebApp.Shared.Application.Utility if (TryGetIndex(result.Id, out var index)) InnerList[index] = result; + var eventArgs = new ItemChangedEventArgs(result, trackingState); + if (OnDataSourceItemChanged != null) await OnDataSourceItemChanged.Invoke(eventArgs); + return result; } @@ -449,7 +620,8 @@ namespace TIAMWebApp.Shared.Application.Utility public bool TryRollbackItem(Guid id, out T? originalValue) { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + try { if (TryGetTrackingItem(id, out var trackingItem)) { @@ -462,22 +634,40 @@ namespace TIAMWebApp.Shared.Application.Utility originalValue = null; return false; } + finally + { + Monitor.Exit(_syncRoot); + } } public void Rollback() { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + try { foreach (var trackingItem in TrackingItems.ToList()) RollbackItemUnsafe(trackingItem); } + finally + { + Monitor.Exit(_syncRoot); + } } public int Count { get { - lock (_syncRoot) return InnerList.Count; + Monitor.Enter(_syncRoot); + try + { + return InnerList.Count; + } + finally + { + Monitor.Exit(_syncRoot); + } + } } @@ -485,17 +675,30 @@ namespace TIAMWebApp.Shared.Application.Utility public void Clear(bool clearChangeTracking) { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + try { if (clearChangeTracking) TrackingItems.Clear(); InnerList.Clear(); } + finally + { + Monitor.Exit(_syncRoot); + } } public int IndexOf(Guid id) { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + try + { return InnerList.FindIndex(x => x.Id == id); + } + finally + { + Monitor.Exit(_syncRoot); + } + } public int IndexOf(T item) => IndexOf(item.Id); @@ -505,22 +708,37 @@ namespace TIAMWebApp.Shared.Application.Utility public bool TryGetValue(Guid id, [NotNullWhen(true)] out T? item) { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + try { item = InnerList.FirstOrDefault(x => x.Id == id); return item != null; } + finally + { + Monitor.Exit(_syncRoot); + } } public void CopyTo(T[] array) => CopyTo(array, 0); public void CopyTo(T[] array, int arrayIndex) { - lock (_syncRoot) InnerList.CopyTo(array, arrayIndex); + Monitor.Enter(_syncRoot); + try + { + InnerList.CopyTo(array, arrayIndex); + } + finally + { + Monitor.Exit(_syncRoot); + } + } public int BinarySearch(int index, int count, T item, IComparer? comparer) { + throw new NotImplementedException($"BinarySearch"); if (index < 0) throw new ArgumentOutOfRangeException(nameof(index)); if (count < 0) @@ -528,8 +746,15 @@ namespace TIAMWebApp.Shared.Application.Utility if (Count - index < count) throw new ArgumentException("Invalid length"); - lock (_syncRoot) - return InnerList.BinarySearch(index, count, item, comparer); + //Monitor.Enter(_syncRoot); + //try + //{ + // return InnerList.BinarySearch(index, count, item, comparer); + //} + //finally + //{ + // Monitor.Exit(_syncRoot); + //} } public int BinarySearch(T item) => BinarySearch(0, Count, item, null); @@ -537,8 +762,17 @@ namespace TIAMWebApp.Shared.Application.Utility public IEnumerator GetEnumerator() { - lock (_syncRoot) - return InnerList.ToList().GetEnumerator(); + Monitor.Enter(_syncRoot); + try + { + //return InnerList.ToList().GetEnumerator(); + return InnerList.GetEnumerator(); + } + finally + { + Monitor.Exit(_syncRoot); + } + } public ReadOnlyCollection AsReadOnly() => new(this); @@ -618,11 +852,17 @@ namespace TIAMWebApp.Shared.Application.Utility try { - lock (_syncRoot) + Monitor.Enter(_syncRoot); + try { //TODO: _list.ToArray() - ez nem az igazi... - J. Array.Copy(InnerList.ToArray(), 0, array!, arrayIndex, InnerList.Count); } + finally + { + Monitor.Exit(_syncRoot); + } + } catch (ArrayTypeMismatchException) { @@ -638,4 +878,16 @@ namespace TIAMWebApp.Shared.Application.Utility #endregion IList, ICollection } + + public class ItemChangedEventArgs where T : IId + { + internal ItemChangedEventArgs(T item, TrackingState trackingState) + { + Item = item; + TrackingState = trackingState; + } + + public T Item { get; } + public TrackingState TrackingState { get; } + } } diff --git a/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs b/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs index e6361fa2..d435cb51 100644 --- a/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs +++ b/TIAMWebApp/Shared/Utility/SignalRDataSourceAsync.cs @@ -16,7 +16,7 @@ public class SignalRDataSourceAsync : SignalRDataSource where T : class, I public Action>? OnDataSourceLoaded; public SignalRDataSourceAsync(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, Guid? contextId = null, Action>? onDataSourceLoaded = null, bool autoLoadDataSource = false) - : base(signalRClient, signalRCrudTags, contextId, false) + : base(signalRClient, signalRCrudTags, contextId) { OnDataSourceLoaded = onDataSourceLoaded; @@ -27,34 +27,34 @@ public class SignalRDataSourceAsync : SignalRDataSource where T : class, I { if (SignalRCrudTags.GetAllMessageTag == SignalRTags.None) throw new ArgumentException($"_signalRCrudTags.GetAllMessageTag == SignalRTags.None;"); - Monitor.Exit(SyncRoot); //Exception test - J. + //Monitor.Exit(SyncRoot); //Exception test - J. - Monitor.Enter(SyncRoot); - try - { - SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId, response => - { - try - { - if (response.Status == SignalResponseStatus.Error) throw new Exception($"LoadDataSourceAsync; response.Status == SignalResponseStatus.Error"); - if (response.ResponseData == null) throw new NullReferenceException($"response.ResponseData == null"); + //Monitor.Enter(SyncRoot); + //try + //{ + // SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId, response => + // { + // try + // { + // if (response.Status == SignalResponseStatus.Error) throw new Exception($"LoadDataSourceAsync; response.Status == SignalResponseStatus.Error"); + // if (response.ResponseData == null) throw new NullReferenceException($"response.ResponseData == null"); - Clear(clearChangeTracking); - InnerList.AddRange(response.ResponseData); - } - finally - { - Monitor.Exit(SyncRoot); - } + // Clear(clearChangeTracking); + // InnerList.AddRange(response.ResponseData); + // } + // finally + // { + // Monitor.Exit(SyncRoot); + // } - OnDataSourceLoaded?.Invoke(this); - }).Forget(); - } - catch (Exception) - { - Monitor.Exit(SyncRoot); - throw; - } + // OnDataSourceLoaded?.Invoke(this); + // }).Forget(); + //} + //catch (Exception) + //{ + // Monitor.Exit(SyncRoot); + // throw; + //} } @@ -101,17 +101,4 @@ public class SignalRDataSourceAsync : SignalRDataSource where T : class, I // return Task.CompletedTask; //} - - public class ItemChangedEventArgs where T : IId - { - internal ItemChangedEventArgs(T item, TrackingState trackingState) - { - Item = item; - TrackingState = trackingState; - } - - public T Item { get; } - public TrackingState TrackingState { get; } - } - } \ No newline at end of file From c349c3c43270e774bb10e1012fca5071e05cd94d Mon Sep 17 00:00:00 2001 From: Loretta Date: Mon, 10 Jun 2024 19:29:25 +0200 Subject: [PATCH 5/5] TiamGrid, SignalRDataSource fixes, improvement; --- .../Shared/Components/Grids/TiamGrid.cs | 230 ++++++++---------- .../Shared/Utility/SignalRDataSource.cs | 229 +++++++++++++---- 2 files changed, 284 insertions(+), 175 deletions(-) diff --git a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs index 545fddc3..f36fb4b7 100644 --- a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs +++ b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs @@ -1,46 +1,19 @@ using System.ComponentModel; -using System.Data.Common; using AyCode.Core; using AyCode.Core.Enums; using AyCode.Core.Extensions; using AyCode.Core.Helpers; using AyCode.Core.Interfaces; -using AyCode.Interfaces.Entities; using AyCode.Services.SignalRs; using AyCode.Utils.Extensions; using DevExpress.Blazor; -using DevExpress.Blazor.Internal; using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; using TIAM.Services; using TIAMWebApp.Shared.Application.Services; using TIAMWebApp.Shared.Application.Utility; namespace TIAMSharedUI.Shared.Components.Grids { - public class GridDataItemChangingEventArgs : GridDataItemChangedEventArgs where TDataItem : class, IId - { - internal GridDataItemChangingEventArgs(TiamGrid grid, TDataItem dataItem, TrackingState trackingState) : base(grid, dataItem, trackingState) - { - } - - public bool IsCanceled { get; set; } - } - - public class GridDataItemChangedEventArgs where TDataItem : class, IId - { - internal GridDataItemChangedEventArgs(TiamGrid grid, TDataItem dataItem, TrackingState trackingState) - { - Grid = grid; - DataItem = dataItem; - TrackingState = trackingState; - } - - public TiamGrid Grid { get; } - public TDataItem DataItem { get; } - public TrackingState TrackingState { get; } - } - public class TiamGrid : DxGrid where TDataItem : class, IId { protected bool IsFirstInitializeParameters; @@ -71,10 +44,12 @@ namespace TIAMSharedUI.Shared.Components.Grids [Parameter] public EventCallback> OnDataSourceChanged { get; set; } [Parameter] public EventCallback> OnGridItemChanging { get; set; } + /// - /// After server response! + /// After the server has responded! /// - [Parameter] public EventCallback> OnGridItemChanged { get; set; } + [Parameter] + public EventCallback> OnGridItemChanged { get; set; } [Parameter] [DefaultValue(null)] @@ -91,27 +66,7 @@ namespace TIAMSharedUI.Shared.Components.Grids return _dataSource!; } - set - { - if (value == null) return; - _dataSourceParam = value; - - //bool equals; - - //if ((equals = Equals(_dataSource, value)) == false) - //{ - // if (value is SignalRDataSource dataSource) - // _dataSource = dataSource; - // else - // { - // var crudTags = new SignalRCrudTags(GetAllMessageTag, SignalRTags.None, AddMessageTag, UpdateMessageTag, RemoveMessageTag); - // _dataSource = new SignalRDataSource(value, SignalRClient, crudTags, ContextId); - // } - //} - - //Data = _dataSource; - //if (!equals) OnDataSourceChanged.InvokeAsync(_dataSource); - } + set => _dataSourceParam = value; } protected override async Task OnInitializedAsync() @@ -127,7 +82,7 @@ namespace TIAMSharedUI.Shared.Components.Grids var crudTags = new SignalRCrudTags(GetAllMessageTag, SignalRTags.None, AddMessageTag, UpdateMessageTag, RemoveMessageTag); _dataSource = new SignalRDataSource(SignalRClient, crudTags, ContextId); - + Data = _dataSource; _dataSource.OnDataSourceLoaded += OnDataSourceLoaded; @@ -136,20 +91,22 @@ namespace TIAMSharedUI.Shared.Components.Grids await base.OnInitializedAsync(); } - private Task OnDataSourceItemChanged(ItemChangedEventArgs args) + private async Task OnDataSourceItemChanged(ItemChangedEventArgs args) { - if (args.TrackingState is TrackingState.GetAll or TrackingState.None) return Task.CompletedTask; + if (args.TrackingState is TrackingState.GetAll or TrackingState.None) return; - Logger.Info($"{_gridLogName} OnItemLoaded; trackingState: {args.TrackingState}"); + Logger.Debug($"{_gridLogName} OnDataSourceItemChanged; trackingState: {args.TrackingState}"); var changedEventArgs = new GridDataItemChangedEventArgs(this, args.Item, args.TrackingState); - return OnGridItemChanged.InvokeAsync(changedEventArgs); + await OnGridItemChanged.InvokeAsync(changedEventArgs); + + await InvokeAsync(StateHasChanged); } private Task OnDataSourceLoaded() { - Logger.Info($"{_gridLogName} OnDataSourceLoaded"); - + Logger.Debug($"{_gridLogName} OnDataSourceLoaded"); + Reload(); return OnDataSourceChanged.InvokeAsync(_dataSource); } @@ -165,28 +122,18 @@ namespace TIAMSharedUI.Shared.Components.Grids } } - public Task AddDataItem(TDataItem dataItem) => AddDataItem(dataItem, AddMessageTag); - public Task AddDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Add); - - public Task UpdateDataItem(TDataItem dataItem) => UpdateDataItem(dataItem, UpdateMessageTag); - public Task UpdateDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Update); - - public Task RemoveDataItem(TDataItem dataItem) => RemoveDataItem(dataItem, RemoveMessageTag); - public Task RemoveDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Remove); - - public Task RemoveDataItem(Guid id) => RemoveDataItem(id, RemoveMessageTag); - - public async Task RemoveDataItem(Guid id, int messageTag) + public Task AddDataItem(TDataItem dataItem) { - var dataItem = _dataSource.FirstOrDefault(x => x.Id == id); - if (dataItem != null) - { - _dataSource.Remove(dataItem); - await _dataSource.SaveChanges(); - //await RemoveDataItem(dataItem); - } + if (dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); + return _dataSource.Add(dataItem, true); + } - await InvokeAsync(StateHasChanged); + public Task AddDataItemAsync(TDataItem dataItem) + { + if (dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); + _dataSource.Add(dataItem); + + return SaveChangesToServerAsync(); } private async Task OnItemSaving(GridEditModelSavingEventArgs e) @@ -196,7 +143,7 @@ namespace TIAMSharedUI.Shared.Components.Grids if (e.IsNew && dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); var logText = e.IsNew ? "add" : "update"; - Logger.Info($"{_gridLogName} OnItemSaving {logText}; Id: {dataItem.Id}"); + Logger.Debug($"{_gridLogName} OnItemSaving {logText}; Id: {dataItem.Id}"); await OnGridEditModelSaving.InvokeAsync(e); @@ -206,29 +153,46 @@ namespace TIAMSharedUI.Shared.Components.Grids return; } - if (!e.IsNew) _dataSource.SetTrackingStateToUpdate(dataItem); - else + if (e.IsNew) await AddDataItemAsync(dataItem); + else await UpdateDataItemAsync(dataItem); + } + + private Task SaveChangesToServerAsync() + { + try { - if (dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); - _dataSource.Add(dataItem); + return _dataSource.SaveChangesAsync(); } + catch (Exception ex) + { + Logger.Error($"{_gridLogName} SaveChangesToServerAsync->SaveChangesAsync error!", ex); + } + + return Task.CompletedTask; + } + + private async Task SaveChangesToServer() + { + var result = false; try { var unsavedItems = await _dataSource.SaveChanges(); - if (unsavedItems.Count > 0) - Logger.Error($"OnItemSaving->TrySaveChanges error! unsavedCount: {unsavedItems.Count}"); + if ((result = unsavedItems.Count == 0) == false) + Logger.Error($"{_gridLogName} SaveChangesToServer->SaveChanges error! unsavedCount: {unsavedItems.Count}"); } catch (Exception ex) { Logger.Error($"{_gridLogName} OnItemSaving", ex); } + + return result; } private async Task OnItemDeleting(GridDataItemDeletingEventArgs e) { - Logger.Info($"{_gridLogName} OnItemDeleting"); + Logger.Debug($"{_gridLogName} OnItemDeleting"); await OnGridItemDeleting.InvokeAsync(e); @@ -239,54 +203,9 @@ namespace TIAMSharedUI.Shared.Components.Grids } var dataItem = (e.DataItem as TDataItem)!; - _dataSource.Remove(dataItem); - - var unsavedItems = await _dataSource.SaveChanges(); - if (unsavedItems.Count > 0) - Logger.Error($"OnItemDeleting->TrySaveChanges error! unsavedCount: {unsavedItems.Count}"); + await RemoveDataItem(dataItem); } - protected virtual async Task PostDataToServerAsync(TDataItem dataItem, int messageTag, TrackingState trackingState) - { - return; - - var changingEventArgs = new GridDataItemChangingEventArgs(this, dataItem, trackingState); - await OnGridItemChanging.InvokeAsync(changingEventArgs); - - if (changingEventArgs.IsCanceled) - { - Logger.Debug($"{_gridLogName} OnDataItemChanging canceled"); - return; - } - - if (messageTag == 0) return; - - Logger.Info($"{_gridLogName} PostDataToServerAsync called; transferId " + dataItem.Id); - - if (dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); - - _dataSource.UpdateCollection(dataItem, trackingState == TrackingState.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. - - //SignalRClient.PostDataAsync(messageTag, dataItem, async response => - //{ - // if (response.Status != SignalResponseStatus.Success || response.ResponseData == null) - // { - // RefreshDataSourceAsync().Forget(); - // return; - // } - - // _dataSource.UpdateCollection(response.ResponseData, trackingState == TrackingState.Remove); - - // var changedEventArgs = new GridDataItemChangedEventArgs(this, response.ResponseData, trackingState); - // await OnDataItemChanged.InvokeAsync(changedEventArgs); - - // InvokeAsync(StateHasChanged).Forget(); - //}).Forget(); - - //transfer = await devAdminSignalClient.PostDataAsync(SignalRTags.UpdateTransferAsync, transfer); - } - - private void OnCustomizeElement(GridCustomizeElementEventArgs e) { if (e.ElementType == GridElementType.DetailCell) @@ -341,5 +260,54 @@ namespace TIAMSharedUI.Shared.Components.Grids _gridLogName = $"[{GridName}]"; } + + //public Task AddDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Add); + + public Task UpdateDataItem(TDataItem dataItem) => _dataSource.Update(dataItem, true); + + public Task UpdateDataItemAsync(TDataItem dataItem) + { + _dataSource.Update(dataItem, false); + return SaveChangesToServerAsync(); + } + //public Task UpdateDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Update); + + public Task AddOrUpdateDataItem(TDataItem dataItem) => _dataSource.AddOrUpdate(dataItem, true); + + public Task RemoveDataItem(TDataItem dataItem) => _dataSource.Remove(dataItem, true); + //public Task RemoveDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Remove); + + public Task RemoveDataItem(Guid id) => RemoveDataItem(id, RemoveMessageTag); + + public Task RemoveDataItem(Guid id, int messageTag) + { + var dataItem = _dataSource.FirstOrDefault(x => x.Id == id); + if (dataItem == null) return Task.CompletedTask; + + return _dataSource.Remove(dataItem, true); + } + } + + public class GridDataItemChangingEventArgs : GridDataItemChangedEventArgs where TDataItem : class, IId + { + internal GridDataItemChangingEventArgs(TiamGrid grid, TDataItem dataItem, TrackingState trackingState) : base(grid, dataItem, trackingState) + { + } + + public bool IsCanceled { get; set; } + } + + public class GridDataItemChangedEventArgs where TDataItem : class, IId + { + internal GridDataItemChangedEventArgs(TiamGrid grid, TDataItem dataItem, TrackingState trackingState) + { + Grid = grid; + DataItem = dataItem; + TrackingState = trackingState; + } + + public TiamGrid Grid { get; } + public TDataItem DataItem { get; } + public TrackingState TrackingState { get; } } } diff --git a/TIAMWebApp/Shared/Utility/SignalRDataSource.cs b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs index 1a6ce0d3..fc9a14f3 100644 --- a/TIAMWebApp/Shared/Utility/SignalRDataSource.cs +++ b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs @@ -131,7 +131,7 @@ namespace TIAMWebApp.Shared.Application.Utility var resultList = (await SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId)) ?? throw new NullReferenceException(); - LoadDataSource(resultList); + await LoadDataSource(resultList); } public Task LoadDataSourceAsync(bool clearChangeTracking = true) @@ -141,9 +141,9 @@ namespace TIAMWebApp.Shared.Application.Utility return SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId, result=> { if (result.Status != SignalResponseStatus.Success || result.ResponseData == null) - throw new NullReferenceException($"result.Status != SignalResponseStatus.Success || result.ResponseData == null; Status: {SignalResponseStatus.Success}"); + throw new NullReferenceException($"LoadDataSourceAsync; result.Status != SignalResponseStatus.Success || result.ResponseData == null; Status: {SignalResponseStatus.Success}"); - LoadDataSource(result.ResponseData); + LoadDataSource(result.ResponseData).Forget(); }); } @@ -225,7 +225,7 @@ namespace TIAMWebApp.Shared.Application.Utility Monitor.Enter(_syncRoot); try { - Update(index, value); + UpdateUnsafe(index, value); } finally { @@ -235,11 +235,6 @@ namespace TIAMWebApp.Shared.Application.Utility } } - /// - /// AddMessageTag - /// - /// - /// public void Add(T newValue) { if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"Add->newValue.Id.IsNullOrEmpty()"); @@ -257,15 +252,37 @@ namespace TIAMWebApp.Shared.Application.Utility { Monitor.Exit(_syncRoot); } + } + /// + /// AddMessageTag + /// + /// + /// + /// + public async Task Add(T newValue, bool autoSave) + { + Monitor.Enter(_syncRoot); + + try + { + Add(newValue); + + return autoSave ? await SaveItem(newValue, TrackingState.Add) : newValue; + } + finally + { + Monitor.Exit(_syncRoot); + } } /// /// AddMessageTag or UpdateMessageTag /// /// + /// /// - public T AddOrUpdate(T newValue) + public async Task AddOrUpdate(T newValue, bool autoSave) { if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"AddOrUpdate->newValue.Id.IsNullOrEmpty()"); @@ -275,7 +292,7 @@ namespace TIAMWebApp.Shared.Application.Utility { var index = IndexOf(newValue); - return index > -1 ? Update(index, newValue) : UnsafeAdd(newValue); + return index > -1 ? await Update(index, newValue, autoSave) : await Add(newValue, autoSave); } finally { @@ -292,12 +309,10 @@ namespace TIAMWebApp.Shared.Application.Utility // } //} - protected T UnsafeAdd(T newValue) + protected void UnsafeAdd(T newValue) { TrackingItems.AddTrackingItem(TrackingState.Add, newValue); InnerList.Add(newValue); - - return newValue; } /// @@ -305,6 +320,7 @@ namespace TIAMWebApp.Shared.Application.Utility /// /// /// + /// /// /// public void Insert(int index, T newValue) @@ -316,7 +332,7 @@ namespace TIAMWebApp.Shared.Application.Utility try { if (Contains(newValue)) - throw new ArgumentException($@"It already contains this Id! Id: {newValue.Id}", nameof(newValue)); + throw new ArgumentException($@"Insert; It already contains this Id! Id: {newValue.Id}", nameof(newValue)); TrackingItems.AddTrackingItem(TrackingState.Add, newValue); InnerList.Insert(index, newValue); @@ -325,28 +341,60 @@ namespace TIAMWebApp.Shared.Application.Utility { Monitor.Exit(_syncRoot); } - } + public async Task Insert(int index, T newValue, bool autoSave) + { + Monitor.Enter(_syncRoot); + + try + { + Insert(index, newValue); + + return autoSave ? await SaveItem(newValue, TrackingState.Add) : newValue; + } + finally + { + Monitor.Exit(_syncRoot); + } + } /// /// UpdateMessageTag /// /// - public T Update(T newItem) => Update(IndexOf(newItem), newItem); + /// + public Task Update(T newItem, bool autoSave) => Update(IndexOf(newItem), newItem, autoSave); /// /// UpdateMessageTag /// /// /// + /// /// /// /// /// /// /// - public T Update(int index, T newValue) + public async Task Update(int index, T newValue, bool autoSave) + { + Monitor.Enter(_syncRoot); + + try + { + UpdateUnsafe(index, newValue); + + return autoSave ? await SaveItem(newValue, TrackingState.Update) : newValue; + } + finally + { + Monitor.Exit(_syncRoot); + } + } + + private void UpdateUnsafe(int index, T newValue) { if (default(T) != null && newValue == null) throw new NullReferenceException(nameof(newValue)); - if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"Update->newValue.Id.IsNullOrEmpty()"); + if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"UpdateUnsafe->newValue.Id.IsNullOrEmpty()"); if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); Monitor.Enter(_syncRoot); @@ -356,20 +404,18 @@ namespace TIAMWebApp.Shared.Application.Utility var currentItem = InnerList[index]; if (currentItem.Id != newValue.Id) - throw new ArgumentException($@"currentItem.Id != item.Id! Id: {newValue.Id}", nameof(newValue)); + throw new ArgumentException($@"UpdateUnsafe; currentItem.Id != item.Id! Id: {newValue.Id}", nameof(newValue)); TrackingItems.AddTrackingItem(TrackingState.Update, newValue, currentItem); InnerList[index] = newValue; - - return newValue; } finally { Monitor.Exit(_syncRoot); } - } + /// /// RemoveMessageTag /// @@ -392,9 +438,26 @@ namespace TIAMWebApp.Shared.Application.Utility { Monitor.Exit(_syncRoot); } - } + public async Task Remove(T item, bool autoSave) + { + Monitor.Enter(_syncRoot); + + try + { + var result = Remove(item); + + if (!autoSave || !result) return result; + + await SaveItem(item, TrackingState.Remove); + return true; + } + finally + { + Monitor.Exit(_syncRoot); + } + } /// /// /// @@ -439,9 +502,27 @@ namespace TIAMWebApp.Shared.Application.Utility { Monitor.Exit(_syncRoot); } - } + public async Task RemoveAt(int index, bool autoSave) + { + Monitor.Enter(_syncRoot); + + try + { + var currentItem = InnerList[index]; + RemoveAt(index); + + if (autoSave) + { + await SaveItem(currentItem, TrackingState.Remove); + } + } + finally + { + Monitor.Exit(_syncRoot); + } + } /// /// /// @@ -518,7 +599,6 @@ namespace TIAMWebApp.Shared.Application.Utility { try { - //throw new Exception(); await SaveTrackingItemUnsafe(trackingItem); } catch(Exception ex) @@ -533,26 +613,49 @@ namespace TIAMWebApp.Shared.Application.Utility { Monitor.Exit(_syncRoot); } + } + public async Task SaveChangesAsync() + { + Monitor.Enter(_syncRoot); + + try + { + foreach (var trackingItem in TrackingItems.ToList()) + { + try + { + await SaveTrackingItemUnsafeAsync(trackingItem); + } + catch(Exception ex) + { + TryRollbackItem(trackingItem.CurrentValue.Id, out _); + } + } + } + finally + { + Monitor.Exit(_syncRoot); + } } /// /// /// /// - /// /// - public async Task SaveItem(Guid id) + public async Task SaveItem(Guid id) { Monitor.Enter(_syncRoot); try { - T? resultItem = null; + T resultItem = null!; if (TryGetTrackingItem(id, out var trackingItem)) resultItem = await SaveTrackingItemUnsafe(trackingItem); + if (resultItem == null) throw new NullReferenceException($"SaveItem; resultItem == null"); return resultItem; } finally @@ -561,17 +664,18 @@ namespace TIAMWebApp.Shared.Application.Utility } } - public async Task SaveItem(Guid id, TrackingState trackingState) + public async Task SaveItem(Guid id, TrackingState trackingState) { Monitor.Enter(_syncRoot); try { - T? resultItem = null; + T resultItem = null!; if (TryGetValue(id, out var item)) resultItem = await SaveItem(item, trackingState); + if (resultItem == null) throw new NullReferenceException($"SaveItem; resultItem == null"); return resultItem; } finally @@ -580,31 +684,68 @@ namespace TIAMWebApp.Shared.Application.Utility } } - public Task SaveItem(T item, TrackingState trackingState) => SaveItemUnsafe(item, trackingState); + public Task SaveItem(T item, TrackingState trackingState) => SaveItemUnsafe(item, trackingState); - protected Task SaveTrackingItemUnsafe(TrackingItem trackingItem) + protected Task SaveTrackingItemUnsafe(TrackingItem trackingItem) => SaveItemUnsafe(trackingItem.CurrentValue, trackingItem.TrackingState); - protected async Task SaveItemUnsafe(T item, TrackingState trackingState) + protected Task SaveTrackingItemUnsafeAsync(TrackingItem trackingItem) + => SaveItemUnsafeAsync(trackingItem.CurrentValue, trackingItem.TrackingState); + + protected async Task SaveItemUnsafe(T item, TrackingState trackingState) { var messageTag = SignalRCrudTags.GetMessageTagByTrackingState(trackingState); - if (messageTag == SignalRTags.None) return null; //throw new ArgumentException($"messageTag == SignalRTags.None"); + if (messageTag == SignalRTags.None) throw new ArgumentException($"SaveItemUnsafe; messageTag == SignalRTags.None"); var result = await SignalRClient.PostDataAsync(messageTag, item); - if (result == null) return null; //throw new NullReferenceException($"result == null"); + if (result == null) throw new NullReferenceException($"SaveItemUnsafe; result == null"); - if (TryGetTrackingItem(item.Id, out var trackingItem)) - TrackingItems.Remove(trackingItem); - - if (TryGetIndex(result.Id, out var index)) - InnerList[index] = result; - - var eventArgs = new ItemChangedEventArgs(result, trackingState); - if (OnDataSourceItemChanged != null) await OnDataSourceItemChanged.Invoke(eventArgs); + await ProcessSavedResponseItem(result, trackingState); return result; } + protected Task SaveItemUnsafeAsync(T item, TrackingState trackingState) + { + var messageTag = SignalRCrudTags.GetMessageTagByTrackingState(trackingState); + if (messageTag == SignalRTags.None) return Task.CompletedTask; + + return SignalRClient.PostDataAsync(messageTag, item, response => + { + Monitor.Enter(_syncRoot); + + try + { + if (response.Status != SignalResponseStatus.Success || response.ResponseData == null) + { + if (TryRollbackItem(item.Id, out _)) return; + + throw new NullReferenceException($"SaveItemUnsafeAsync; result.Status != SignalResponseStatus.Success || result.ResponseData == null; Status: {SignalResponseStatus.Success}"); + } + + ProcessSavedResponseItem(response.ResponseData, trackingState).Forget(); + } + finally + { + Monitor.Exit(_syncRoot); + } + }); + } + + private async Task ProcessSavedResponseItem(T? resultItem, TrackingState trackingState) + { + if (resultItem == null) return; + + if (TryGetTrackingItem(resultItem.Id, out var trackingItem)) + TrackingItems.Remove(trackingItem); + + if (TryGetIndex(resultItem.Id, out var index)) + InnerList[index] = resultItem; + + var eventArgs = new ItemChangedEventArgs(resultItem, trackingState); + if (OnDataSourceItemChanged != null) await OnDataSourceItemChanged.Invoke(eventArgs); + } + protected void RollbackItemUnsafe(TrackingItem trackingItem) { if (TryGetIndex(trackingItem.CurrentValue.Id, out var index))