diff --git a/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs b/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs index 49da0875..f6192029 100644 --- a/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs +++ b/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs @@ -95,7 +95,7 @@ public void LoginPageViewModel_LoginCommand_Execute_ConfigUrlSet_IsExecuted(Stri this.Mediator.Verify(x => x.Send(It.IsAny(), It.IsAny()), Times.Once); this.NavigationService.Verify(n => n.GoToHome(), Times.Once); if (String.IsNullOrEmpty(configUrl) == false){ - this.ApplicationCache.Verify(v => v.SetConfigHostUrl(It.IsAny(), It.IsAny()), Times.Once); + this.ApplicationCache.Verify(v => v.SetConfigHostUrl(It.IsAny(), It.IsAny()), Times.Once); } } diff --git a/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/MyAccount/MyAccountPageViewModelTests.cs b/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/MyAccount/MyAccountPageViewModelTests.cs index ccc7ba53..0ef73a7d 100644 --- a/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/MyAccount/MyAccountPageViewModelTests.cs +++ b/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/MyAccount/MyAccountPageViewModelTests.cs @@ -56,7 +56,7 @@ public async Task MyAccountPageViewModel_Initialise_IsInitialised() { this.ViewModel.MerchantName.ShouldBe(TestData.MerchantDetailsModel.MerchantName); this.ViewModel.LastLogin.ShouldBe(DateTime.Now, TimeSpan.FromSeconds(30)); this.ViewModel.IsDarkThemeEnabled.ShouldBeTrue(); - this.ApplicationCache.Verify(a => a.SetMerchantDetails(It.IsAny(), It.IsAny()), Times.Once); + this.ApplicationCache.Verify(a => a.SetMerchantDetails(It.IsAny(), It.IsAny()), Times.Once); } [Fact] diff --git a/TransactionProcessor.Mobile.BusinessLogic/Services/ApplicationCache.cs b/TransactionProcessor.Mobile.BusinessLogic/Services/ApplicationCache.cs index 1640bdcf..ab5de460 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/Services/ApplicationCache.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/Services/ApplicationCache.cs @@ -1,10 +1,11 @@ -using System; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Primitives; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Memory; using TransactionProcessor.Mobile.BusinessLogic.Models; namespace TransactionProcessor.Mobile.BusinessLogic.Services @@ -13,43 +14,41 @@ public interface IApplicationCache { String GetConfigHostUrl(); - void SetConfigHostUrl(String value, MemoryCacheEntryOptions options = default); + void SetConfigHostUrl(String value, Int32 timeout=60); Boolean GetUseTrainingMode(); - void SetUseTrainingMode(Boolean value, MemoryCacheEntryOptions options = default); + void SetUseTrainingMode(Boolean value, Int32 timeout = 60); Boolean GetIsLoggedIn(); - void SetIsLoggedIn(Boolean value, MemoryCacheEntryOptions options = default); + void SetIsLoggedIn(Boolean value, Int32 timeout = 60); Configuration GetConfiguration(); - void SetConfiguration(Configuration value, MemoryCacheEntryOptions options = default); + void SetConfiguration(Configuration value, Int32 timeout = 60); List GetContractProducts(); - void SetContractProducts(List value, MemoryCacheEntryOptions options = default); + void SetContractProducts(List value, Int32 timeout = 60); TokenResponseModel GetAccessToken(); - void SetAccessToken(TokenResponseModel value, MemoryCacheEntryOptions options = default); - + void SetAccessToken(TokenResponseModel value, MemoryCacheEntryOptions cacheEntryOptions = null); Guid GetEstateId(); - void SetEstateId(Guid value, MemoryCacheEntryOptions options = default); + void SetEstateId(Guid value, Int32 timeout = 60); Guid GetMerchantId(); - void SetMerchantId(Guid value, MemoryCacheEntryOptions options = default); - + void SetMerchantId(Guid value, Int32 timeout = 60); MerchantDetailsModel GetMerchantDetails(); - void SetMerchantDetails(MerchantDetailsModel value, MemoryCacheEntryOptions options = default); + void SetMerchantDetails(MerchantDetailsModel value, Int32 timeout = 60); Decimal GetMerchantBalance(); - void SetMerchantBalance(Decimal value, MemoryCacheEntryOptions options = default); + void SetMerchantBalance(Decimal value); } [ExcludeFromCodeCoverage] @@ -112,9 +111,9 @@ public String GetConfigHostUrl() } public void SetConfigHostUrl(String value, - MemoryCacheEntryOptions options = default) + Int32 timeout = 60) { - this.Set("ConfigHostUrl", value, options); + this.Set("ConfigHostUrl", value, timeout); } public Boolean GetUseTrainingMode() @@ -123,39 +122,39 @@ public Boolean GetUseTrainingMode() } public void SetAccessToken(TokenResponseModel value, - MemoryCacheEntryOptions options = default) + MemoryCacheEntryOptions cacheEntryOptions = null) { - this.Set("AccessToken", value, options); + this.Set("AccessToken", value, cacheEntryOptions); } public void SetConfiguration(Configuration value, - MemoryCacheEntryOptions options = default) + Int32 timeout = 60) { - this.Set("Configuration", value, options); + this.Set("Configuration", value, timeout); } public void SetContractProducts(List value, - MemoryCacheEntryOptions options = default) + Int32 timeout = 60) { - this.Set("ContractProducts", value, options); + this.Set("ContractProducts", value, timeout); } public void SetEstateId(Guid value, - MemoryCacheEntryOptions options = default) + Int32 timeout = 60) { - this.Set("EstateId", value, options); + this.Set("EstateId", value, timeout); } public void SetIsLoggedIn(Boolean value, - MemoryCacheEntryOptions options = default) + Int32 timeout = 60) { - this.Set("isLoggedIn", value, options); + this.Set("isLoggedIn", value, timeout); } public void SetMerchantId(Guid value, - MemoryCacheEntryOptions options = default) + Int32 timeout = 60) { - this.Set("MerchantId", value, options); + this.Set("MerchantId", value, timeout); } public MerchantDetailsModel GetMerchantDetails() @@ -164,31 +163,47 @@ public MerchantDetailsModel GetMerchantDetails() } public void SetMerchantDetails(MerchantDetailsModel value, - MemoryCacheEntryOptions options = default) + Int32 timeout = 60) { - this.Set("MerchantDetails", value, options); + this.Set("MerchantDetails", value, timeout); } public Decimal GetMerchantBalance() { return this.TryGetValue("MerchantBalance"); } - public void SetMerchantBalance(Decimal value, - MemoryCacheEntryOptions options = default) { - this.Set("MerchantBalance", value, options); + public void SetMerchantBalance(Decimal value) { + this.Set("MerchantBalance", value, 60); } public void SetUseTrainingMode(Boolean value, - MemoryCacheEntryOptions options = default) + Int32 timeout = 60) + { + this.Set("UseTrainingMode", value, timeout); + } + + private void Set(String key, + T cache, + Int32 timeout) { - this.Set("UseTrainingMode", value, options); + DateTime expirationTime = DateTime.Now.AddSeconds(timeout); + CancellationChangeToken expirationToken = new(new CancellationTokenSource(TimeSpan.FromSeconds(timeout)).Token); + MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions() + // Pin to cache. + .SetPriority(CacheItemPriority.NeverRemove) + // Set the actual expiration time + .SetAbsoluteExpiration(expirationTime) + // Force eviction to run + .AddExpirationToken(expirationToken); + + this.MemoryCache.Set(key, cache, cacheEntryOptions); } private void Set(String key, T cache, - MemoryCacheEntryOptions options) + MemoryCacheEntryOptions cacheEntryOptions) { - this.MemoryCache.Set(key, cache, options); + this.MemoryCache.Set(key, cache, cacheEntryOptions); } private T TryGetValue(String Key) diff --git a/TransactionProcessor.Mobile.BusinessLogic/Services/BalanceRefresher.cs b/TransactionProcessor.Mobile.BusinessLogic/Services/BalanceRefresher.cs index d87e59a1..7ec7d789 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/Services/BalanceRefresher.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/Services/BalanceRefresher.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; +using System.Threading.Tasks; using TransactionProcessor.Mobile.BusinessLogic.Logging; namespace TransactionProcessor.Mobile.BusinessLogic.Services @@ -25,7 +27,8 @@ public BalanceRefresher(IApplicationCache applicationCache, IMerchantService mer public void StartRefreshing() { _cts = new CancellationTokenSource(); - _ = RefreshLoopAsync(_cts.Token); + // Run the refresh loop on a background thread so it does not run on the main UI thread. + _ = Task.Run(() => RefreshLoopAsync(_cts.Token)); } public void StopRefreshing() @@ -40,11 +43,11 @@ private async Task RefreshLoopAsync(CancellationToken token) try { // Do the first refresh - await RefreshBalanceAsync(); + await RefreshBalanceAsync(token).ConfigureAwait(false); - while (await timer.WaitForNextTickAsync(token)) + while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false)) { - await RefreshBalanceAsync(); + await RefreshBalanceAsync(token).ConfigureAwait(false); } } catch (OperationCanceledException) @@ -52,16 +55,17 @@ private async Task RefreshLoopAsync(CancellationToken token) } } - private async Task RefreshBalanceAsync() + private async Task RefreshBalanceAsync(CancellationToken token) { - decimal balance = await GetBalanceFromApi(); + decimal balance = await GetBalanceFromApi(token).ConfigureAwait(false); + // Setting the cache is lightweight; do on background thread to avoid UI thread work. this.ApplicationCache.SetMerchantBalance(balance); } - private async Task GetBalanceFromApi() + private async Task GetBalanceFromApi(CancellationToken token) { - var result = await this.MerchantService.GetMerchantBalance(CancellationToken.None); + var result = await this.MerchantService.GetMerchantBalance(token).ConfigureAwait(false); if (result.IsSuccess) { return result.Data; diff --git a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/LoginPageViewModel.cs b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/LoginPageViewModel.cs index 0a422647..01d584db 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/LoginPageViewModel.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/LoginPageViewModel.cs @@ -146,17 +146,7 @@ private async Task>> GetMerchantContractProduc private void CacheContractData(List contractProductModels) { - DateTime expirationTime = DateTime.Now.AddMinutes(60); - CancellationChangeToken expirationToken = new CancellationChangeToken(new CancellationTokenSource(TimeSpan.FromMinutes(60)).Token); - MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions() - // Pin to cache. - .SetPriority(CacheItemPriority.NeverRemove) - // Set the actual expiration time - .SetAbsoluteExpiration(expirationTime) - // Force eviction to run - .AddExpirationToken(expirationToken); - - this.ApplicationCache.SetContractProducts(contractProductModels, cacheEntryOptions); + this.ApplicationCache.SetContractProducts(contractProductModels, 600); } private async Task CheckForUpdates(Configuration configuration) { diff --git a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountAddressPageViewModel.cs b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountAddressPageViewModel.cs index d68bb689..22b73427 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountAddressPageViewModel.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountAddressPageViewModel.cs @@ -54,18 +54,8 @@ public async Task Initialise(CancellationToken cancellationToken) { await this.DialogService.ShowWarningToast("Unable to load merchant details. Please try again later.", cancellationToken: cancellationToken); return; } - - DateTime expirationTime = DateTime.Now.AddMinutes(60); - CancellationChangeToken expirationToken = new(new CancellationTokenSource(TimeSpan.FromMinutes(60)).Token); - MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions() - // Pin to cache. - .SetPriority(CacheItemPriority.NeverRemove) - // Set the actual expiration time - .SetAbsoluteExpiration(expirationTime) - // Force eviction to run - .AddExpirationToken(expirationToken); - - this.ApplicationCache.SetMerchantDetails(merchantDetailsResult.Data, cacheEntryOptions); + + this.ApplicationCache.SetMerchantDetails(merchantDetailsResult.Data, 3600); merchantDetails = this.ApplicationCache.GetMerchantDetails(); } diff --git a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountContactPageViewModel.cs b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountContactPageViewModel.cs index f0f46532..1387b6b5 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountContactPageViewModel.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountContactPageViewModel.cs @@ -48,17 +48,7 @@ public async Task Initialise(CancellationToken cancellationToken) { return; } - DateTime expirationTime = DateTime.Now.AddMinutes(60); - CancellationChangeToken expirationToken = new(new CancellationTokenSource(TimeSpan.FromMinutes(60)).Token); - MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions() - // Pin to cache. - .SetPriority(CacheItemPriority.NeverRemove) - // Set the actual expiration time - .SetAbsoluteExpiration(expirationTime) - // Force eviction to run - .AddExpirationToken(expirationToken); - - this.ApplicationCache.SetMerchantDetails(merchantDetailsResult.Data, cacheEntryOptions); + this.ApplicationCache.SetMerchantDetails(merchantDetailsResult.Data, 3600); merchantDetails = this.ApplicationCache.GetMerchantDetails(); } diff --git a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountPageViewModel.cs b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountPageViewModel.cs index 894021b3..664effc0 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountPageViewModel.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/MyAccount/MyAccountPageViewModel.cs @@ -85,17 +85,7 @@ public async Task Initialise(CancellationToken cancellationToken) { this.MerchantName = merchantDetailsResult.Data.MerchantName; - DateTime expirationTime = DateTime.Now.AddMinutes(60); - CancellationChangeToken expirationToken = new(new CancellationTokenSource(TimeSpan.FromMinutes(60)).Token); - MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions() - // Pin to cache. - .SetPriority(CacheItemPriority.NeverRemove) - // Set the actual expiration time - .SetAbsoluteExpiration(expirationTime) - // Force eviction to run - .AddExpirationToken(expirationToken); - - this.ApplicationCache.SetMerchantDetails(merchantDetailsResult.Data, cacheEntryOptions); + this.ApplicationCache.SetMerchantDetails(merchantDetailsResult.Data, 3600); this.LastLogin = DateTime.Now; // TODO: might cache this in the application this.IsDarkThemeEnabled = await this.ApplicationThemeService.GetDarkThemeEnabled(); diff --git a/TransactionProcessor.Mobile/MauiProgram.cs b/TransactionProcessor.Mobile/MauiProgram.cs index 2013cef1..1a86314f 100644 --- a/TransactionProcessor.Mobile/MauiProgram.cs +++ b/TransactionProcessor.Mobile/MauiProgram.cs @@ -8,6 +8,7 @@ using TransactionProcessor.Mobile.Extensions; using TransactionProcessor.Mobile.UIServices; using Microsoft.Extensions.DependencyInjection; +//using Sentry; namespace TransactionProcessor.Mobile { @@ -25,6 +26,7 @@ public static MauiApp CreateMauiApp() .ConfigureAppServices() .ConfigureUIServices() .ConfigureDatabase() + //.ConfigureSentry() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }).Services.AddTransient() @@ -33,7 +35,7 @@ public static MauiApp CreateMauiApp() builder.Logging.SetMinimumLevel(LogLevel.Trace);//.AddConsole(); Container = builder.Build(); - + Logger.Initialise(new ConsoleLogger()); return Container;