From 760e17b655edaf998d19d3b0304bc5d5036d2838 Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Wed, 1 May 2024 15:28:20 -0300 Subject: [PATCH 01/10] Updated ISessionManager and SessionManager with timeout and cancellation support Updated the `ISessionManager` interface and `SessionManager` class to include a `TimeSpan` parameter in the `RetrieveSession` and `SendSession` methods for timeout specification. Overloads of these methods have been added to accept a `CancellationToken` parameter for operation cancellation. The `StateManager` class's `GetState` and `SaveState` methods have also been updated to accept a `TimeSpan` parameter for timeout. Added a new project `Csla.Blazor.WebAssembly.Tests` for unit tests of the `SessionManager` class, including tests for timeout and cancellation scenarios. The solution file and a new project settings file have been updated accordingly. The `SessionManager` class now handles `OperationCanceledException` by rethrowing the exception. --- .../Blazor/State/SessionManager.cs | 4 +- .../Csla.Blazor.WebAssembly.Tests.csproj | 36 +++ .../SessionManagerTests.cs | 205 ++++++++++++++++++ .../State/SessionManager.cs | 79 +++++-- Source/Csla.Blazor/State/StateManager.cs | 12 +- Source/Csla/State/ISessionManager.cs | 4 +- Source/csla.test.sln | 42 ++++ 7 files changed, 358 insertions(+), 24 deletions(-) create mode 100644 Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj create mode 100644 Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs diff --git a/Source/Csla.AspNetCore/Blazor/State/SessionManager.cs b/Source/Csla.AspNetCore/Blazor/State/SessionManager.cs index 6d968673c9..fba25e0108 100644 --- a/Source/Csla.AspNetCore/Blazor/State/SessionManager.cs +++ b/Source/Csla.AspNetCore/Blazor/State/SessionManager.cs @@ -79,8 +79,8 @@ public void PurgeSessions(TimeSpan expiration) } // wasm client-side methods - Task ISessionManager.RetrieveSession() => throw new NotImplementedException(); + Task ISessionManager.RetrieveSession(TimeSpan timeout) => throw new NotImplementedException(); Session ISessionManager.GetCachedSession() => throw new NotImplementedException(); - Task ISessionManager.SendSession() => throw new NotImplementedException(); + Task ISessionManager.SendSession(TimeSpan timeout) => throw new NotImplementedException(); } } diff --git a/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj b/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj new file mode 100644 index 0000000000..107042eeeb --- /dev/null +++ b/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj @@ -0,0 +1,36 @@ + + + + net8.0 + false + + Library + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs new file mode 100644 index 0000000000..1376cd0ca9 --- /dev/null +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -0,0 +1,205 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Threading; +using System.Threading.Tasks; +using Csla.Blazor.WebAssembly.State; +using Csla.State; +using Moq; +using System.Net.Http; +using Csla.Blazor.WebAssembly.Configuration; +using Csla.Core; +using Csla.Runtime; +using Moq.Protected; +using System.Net; +using Csla.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using Csla.Serialization.Mobile; +using Microsoft.AspNetCore.Components.Authorization; + +namespace Csla.Test.State +{ + [TestClass] + public class SessionManagerTests + { + private SessionManager _sessionManager; + + [TestInitialize] + public void Initialize() + { + var mockServiceProvider = new Mock(); + + // Mock AuthenticationStateProvider + var mockAuthStateProvider = new Mock(); + + // Mock IServiceProvider + mockServiceProvider.Setup(x => x.GetService(typeof(AuthenticationStateProvider))).Returns(mockAuthStateProvider.Object); + + var session = new SessionMessage + { + // Set properties here + // For example: + Principal = new Security.CslaClaimsPrincipal() { }, + Session = [] + }; + + // Mock ISerializationFormatter + var mockFormatter = new Mock(); + mockFormatter.Setup(x => x.Serialize(It.IsAny(), It.IsAny())); + mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(session); + + // Mock IServiceProvider + mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter.Object); + + var mockActivator = new Mock(); + mockActivator.Setup(x => x.CreateInstance(It.Is(t => t == typeof(Csla.Serialization.Mobile.MobileFormatter)))).Returns(mockFormatter.Object); + mockActivator.Setup(x => x.InitializeInstance(It.IsAny())); + + // Mock IServiceProvider + mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Server.IDataPortalActivator))).Returns(mockActivator.Object); + + // Mock IServiceProvider + + // Mock IContextManager + var mockContextManager = new Mock(); + mockContextManager.Setup(x => x.IsValid).Returns(true); + + // Mock IContextManagerLocal + var mockLocalContextManager = new Mock(); + + // Mock IServiceProvider + mockServiceProvider.Setup(x => x.GetService(typeof(IRuntimeInfo))).Returns(new RuntimeInfo()); + + // Mock IEnumerable + var mockContextManagerList = new List { mockContextManager.Object }; + + // Mock ApplicationContextAccessor + var mockApplicationContextAccessor = new Mock(mockContextManagerList, mockLocalContextManager.Object, mockServiceProvider.Object); + + var _applicationContext = new ApplicationContext(mockApplicationContextAccessor.Object); + + + _sessionManager = new SessionManager(_applicationContext, GetHttpClient(session, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); + _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); + } + + private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext) + { + var handlerMock = new Mock(MockBehavior.Strict); + handlerMock + .Protected() + // Setup the PROTECTED method to mock + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + // prepare the expected response of the mocked http call + .ReturnsAsync((HttpRequestMessage request, CancellationToken cancellationToken) => + { + + + if (cancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException(cancellationToken); + } + else + { + return new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{\"ResultStatus\":0, \"SessionData\":\"" + Convert.ToBase64String(GetSession(session, _applicationContext)) + "\"}"), + }; + } + }) + .Verifiable(); + + // use real http client with mocked handler here + var httpClient = new HttpClient(handlerMock.Object) + { + BaseAddress = new Uri("http://test.com/"), + }; + return httpClient; + } + + private static byte[] GetSession(SessionMessage session, ApplicationContext _applicationContext) + { + var info = new MobileFormatter(_applicationContext).SerializeToDTO(session); + + var ms = new MemoryStream(); + + new CslaBinaryWriter(_applicationContext).Write(ms, info); + + ms.Position = 0; + var array = ms.ToArray(); + return array; + } + + [TestMethod] + public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException() + { + var timeout = TimeSpan.FromHours(1); + await _sessionManager.RetrieveSession(timeout); + } + + [TestMethod] + public async Task RetrieveSession_WithZeroTimeout_ShouldNotThrowException() + { + var timeout = TimeSpan.Zero; + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); + + } + + [TestMethod] + public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowException() + { + var cts = new CancellationTokenSource(); + await _sessionManager.RetrieveSession(cts.Token); + } + + [TestMethod] + public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowOperationCanceledException() + { + var cts = new CancellationTokenSource(); + cts.Cancel(); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); + } + + [TestMethod] + public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() + { + var timeout = TimeSpan.FromHours(1); + await _sessionManager.SendSession(timeout); + } + + [TestMethod] + public async Task SendSession_WithZeroTimeout_ShouldNotThrowException() + { + var timeout = TimeSpan.Zero; + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); + + } + + [TestMethod] + public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException() + { + var cts = new CancellationTokenSource(); + await _sessionManager.SendSession(cts.Token); + } + + [TestMethod] + public async Task SendSession_WithCancelledCancellationToken_ShouldThrowOperationCanceledException() + { + var cts = new CancellationTokenSource(); + cts.Cancel(); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); + } + + + [TestMethod] + public void RetrieveCAchedSessionSession() + { + + _sessionManager.GetCachedSession(); + } + } +} diff --git a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs index b8c61c0328..4f2fcb08dc 100644 --- a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs +++ b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs @@ -44,7 +44,17 @@ public Session GetCachedSession() /// the web server to the wasm client /// if SyncContextWithServer is true. /// - public async Task RetrieveSession() + public Task RetrieveSession(TimeSpan timeout) + { + return RetrieveSession(GetCancellationToken(timeout)); + } + + /// + /// Retrieves the current user's session from + /// the web server to the wasm client + /// if SyncContextWithServer is true. + /// + public async Task RetrieveSession(CancellationToken ct) { if (_options.SyncContextWithServer) { @@ -53,25 +63,35 @@ public async Task RetrieveSession() lastTouched = _session.LastTouched; var url = $"{_options.StateControllerName}?lastTouched={lastTouched}"; - var stateResult = await client.GetFromJsonAsync(url); - if (stateResult.ResultStatus == ResultStatuses.Success) + try { - var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); - var buffer = new MemoryStream(stateResult.SessionData) + var stateResult = await client.GetFromJsonAsync(url, ct); + + + if (stateResult.ResultStatus == ResultStatuses.Success) { - Position = 0 - }; - var message = (SessionMessage)formatter.Deserialize(buffer); - _session = message.Session; - if (message.Principal is not null && - ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) + var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); + var buffer = new MemoryStream(stateResult.SessionData) + { + Position = 0 + }; + var message = (SessionMessage)formatter.Deserialize(buffer); + _session = message.Session; + if (message.Principal is not null && + ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) + { + provider.SetPrincipal(message.Principal); + } + } + else // NoUpdates { - provider.SetPrincipal(message.Principal); + _session = GetSession(); } } - else // NoUpdates + catch(OperationCanceledException) { _session = GetSession(); + throw; } } else @@ -86,7 +106,29 @@ public async Task RetrieveSession() /// the wasm client to the web server /// if SyncContextWithServer is true. /// - public async Task SendSession() + public Task SendSession(TimeSpan timeout) + { + return SendSession(GetCancellationToken(timeout)); + } + + private static CancellationToken GetCancellationToken(TimeSpan timeout) + { + var cts = new CancellationTokenSource(); + cts.CancelAfter(timeout); + if (timeout <= TimeSpan.Zero) + { + cts.Cancel(); + } + + return cts.Token; + } + + /// + /// Sends the current user's session from + /// the wasm client to the web server + /// if SyncContextWithServer is true. + /// + public async Task SendSession(CancellationToken ct) { _session.Touch(); if (_options.SyncContextWithServer) @@ -95,7 +137,14 @@ public async Task SendSession() var buffer = new MemoryStream(); formatter.Serialize(buffer, _session); buffer.Position = 0; - await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray()); + try + { + await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); + } + catch(OperationCanceledException ) + { + throw; + } } } diff --git a/Source/Csla.Blazor/State/StateManager.cs b/Source/Csla.Blazor/State/StateManager.cs index 01cb84dc41..576346efe7 100644 --- a/Source/Csla.Blazor/State/StateManager.cs +++ b/Source/Csla.Blazor/State/StateManager.cs @@ -39,12 +39,14 @@ public Task InitializeAsync(TimeSpan timeout) /// Get state from cache. /// /// Time to wait before timing out - private async Task GetState(TimeSpan timeout) + private async Task GetState(TimeSpan timeout) { - Session session; + Session session = null; var isBrowser = OperatingSystem.IsBrowser(); if (isBrowser) - session = await _sessionManager.RetrieveSession(); + session = await _sessionManager.RetrieveSession(timeout); + + return session; } /// @@ -57,12 +59,12 @@ private async Task GetState(TimeSpan timeout) /// at which you know the user is navigating to another /// page. /// - public void SaveState() + public void SaveState(TimeSpan timeout) { var isBrowser = OperatingSystem.IsBrowser(); if (isBrowser) { - _sessionManager.SendSession(); + _sessionManager.SendSession(timeout); } } } diff --git a/Source/Csla/State/ISessionManager.cs b/Source/Csla/State/ISessionManager.cs index 6ce61594d0..c089102e62 100644 --- a/Source/Csla/State/ISessionManager.cs +++ b/Source/Csla/State/ISessionManager.cs @@ -18,7 +18,7 @@ public interface ISessionManager /// Retrieves the current user's session from /// the web server to the wasm client. /// - Task RetrieveSession(); + Task RetrieveSession(TimeSpan timeout); /// /// Gets the current user's session from the cache. /// @@ -27,7 +27,7 @@ public interface ISessionManager /// Sends the current user's session from /// the wasm client to the web server. /// - Task SendSession(); + Task SendSession(TimeSpan timeout); #endregion #region Server diff --git a/Source/csla.test.sln b/Source/csla.test.sln index 77fc837132..3db51aa411 100644 --- a/Source/csla.test.sln +++ b/Source/csla.test.sln @@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphMergerTest.Business", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphMergerTest.BusinessTests", "GraphMergerTest\GraphMergerTest.BusinessTests\GraphMergerTest.BusinessTests.csproj", "{FE652EBA-94E7-4BB2-99C8-18B0382E0D9A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Blazor.WebAssembly.Tests", "Csla.Blazor.WebAssembly.Tests\Csla.Blazor.WebAssembly.Tests.csproj", "{430DDCF1-1570-4193-9B1E-774303F6FC34}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1006,6 +1008,46 @@ Global {FE652EBA-94E7-4BB2-99C8-18B0382E0D9A}.Testing|x64.Build.0 = Debug|Any CPU {FE652EBA-94E7-4BB2-99C8-18B0382E0D9A}.Testing|x86.ActiveCfg = Debug|Any CPU {FE652EBA-94E7-4BB2-99C8-18B0382E0D9A}.Testing|x86.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|ARM.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|ARM.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|x64.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|x64.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|x86.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|x86.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|ARM.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|ARM.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|x64.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|x64.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|x86.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|x86.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|Any CPU.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|Any CPU.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|ARM.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|ARM.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|x64.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|x64.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|x86.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|x86.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|Any CPU.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|ARM.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|ARM.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|x64.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|x64.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|x86.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|x86.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|Any CPU.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|Any CPU.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|ARM.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|ARM.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|x64.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|x64.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|x86.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d54e552a5c96f5a268f6692d81cd716693fac91a Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Wed, 1 May 2024 17:40:23 -0300 Subject: [PATCH 02/10] `Refactor SessionManager and StateManager for improved session handling` Refactored `SessionManagerTests.cs` and `SessionManager.cs` to improve session handling. The `SessionMessage` object has been changed from a local variable to a class-level variable, allowing it to be accessed across different methods. The `RetrieveSession` and `SendSession` methods have been updated to return the session and assert that the returned session is equal to `SessionValue.Session`. The exception type expected in these methods with zero timeout and cancelled cancellation token has been changed from `TaskCanceledException` to `TimeoutException`. The `GetCancellationToken` method has been simplified to create a `CancellationTokenSource` with a timeout directly. In `StateManager.cs`, the `GetState` method has been updated to not return a `Session` object, instead, it just retrieves the session without assigning it to a variable. --- .../SessionManagerTests.cs | 33 +++++++++++-------- .../State/SessionManager.cs | 31 ++++++++--------- Source/Csla.Blazor/State/StateManager.cs | 7 ++-- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index 1376cd0ca9..a30751b77d 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -22,6 +22,7 @@ namespace Csla.Test.State public class SessionManagerTests { private SessionManager _sessionManager; + private SessionMessage SessionValue; [TestInitialize] public void Initialize() @@ -34,7 +35,7 @@ public void Initialize() // Mock IServiceProvider mockServiceProvider.Setup(x => x.GetService(typeof(AuthenticationStateProvider))).Returns(mockAuthStateProvider.Object); - var session = new SessionMessage + SessionValue = new SessionMessage { // Set properties here // For example: @@ -45,7 +46,7 @@ public void Initialize() // Mock ISerializationFormatter var mockFormatter = new Mock(); mockFormatter.Setup(x => x.Serialize(It.IsAny(), It.IsAny())); - mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(session); + mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(SessionValue); // Mock IServiceProvider mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter.Object); @@ -78,7 +79,7 @@ public void Initialize() var _applicationContext = new ApplicationContext(mockApplicationContextAccessor.Object); - _sessionManager = new SessionManager(_applicationContext, GetHttpClient(session, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); + _sessionManager = new SessionManager(_applicationContext, GetHttpClient(SessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); } @@ -138,14 +139,15 @@ private static byte[] GetSession(SessionMessage session, ApplicationContext _app public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); - await _sessionManager.RetrieveSession(timeout); + var session = await _sessionManager.RetrieveSession(timeout); + Assert.AreEqual(session, SessionValue.Session); } [TestMethod] - public async Task RetrieveSession_WithZeroTimeout_ShouldNotThrowException() + public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); } @@ -153,15 +155,17 @@ public async Task RetrieveSession_WithZeroTimeout_ShouldNotThrowException() public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowException() { var cts = new CancellationTokenSource(); - await _sessionManager.RetrieveSession(cts.Token); + var session = await _sessionManager.RetrieveSession(cts.Token); + Assert.AreEqual(session, SessionValue.Session); + } [TestMethod] - public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowOperationCanceledException() + public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTimeoutException() { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); } [TestMethod] @@ -169,13 +173,15 @@ public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); await _sessionManager.SendSession(timeout); + + Assert.IsTrue(true); } [TestMethod] - public async Task SendSession_WithZeroTimeout_ShouldNotThrowException() + public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); } @@ -184,14 +190,15 @@ public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException { var cts = new CancellationTokenSource(); await _sessionManager.SendSession(cts.Token); + Assert.IsTrue(true); } [TestMethod] - public async Task SendSession_WithCancelledCancellationToken_ShouldThrowOperationCanceledException() + public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTimeoutException() { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); } diff --git a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs index 4f2fcb08dc..a43f5494fd 100644 --- a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs +++ b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs @@ -13,6 +13,8 @@ using Csla.State; using Microsoft.AspNetCore.Components.Authorization; using Csla.Blazor.State.Messages; +using System.Reflection; +using System.Threading; namespace Csla.Blazor.WebAssembly.State { @@ -49,12 +51,12 @@ public Task RetrieveSession(TimeSpan timeout) return RetrieveSession(GetCancellationToken(timeout)); } - /// - /// Retrieves the current user's session from - /// the web server to the wasm client - /// if SyncContextWithServer is true. - /// - public async Task RetrieveSession(CancellationToken ct) + /// + /// Retrieves the current user's session from + /// the web server to the wasm client + /// if SyncContextWithServer is true. + /// + public async Task RetrieveSession(CancellationToken ct) { if (_options.SyncContextWithServer) { @@ -88,10 +90,9 @@ public async Task RetrieveSession(CancellationToken ct) _session = GetSession(); } } - catch(OperationCanceledException) + catch(OperationCanceledException ocex) { - _session = GetSession(); - throw; + throw new TimeoutException ($"{this.GetType().FullName}.RetrieveSession.", ocex); } } else @@ -113,13 +114,7 @@ public Task SendSession(TimeSpan timeout) private static CancellationToken GetCancellationToken(TimeSpan timeout) { - var cts = new CancellationTokenSource(); - cts.CancelAfter(timeout); - if (timeout <= TimeSpan.Zero) - { - cts.Cancel(); - } - + var cts = new CancellationTokenSource(timeout); return cts.Token; } @@ -141,9 +136,9 @@ public async Task SendSession(CancellationToken ct) { await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); } - catch(OperationCanceledException ) + catch(OperationCanceledException ocex) { - throw; + throw new TimeoutException($"{this.GetType().FullName}.SendSession.", ocex); } } } diff --git a/Source/Csla.Blazor/State/StateManager.cs b/Source/Csla.Blazor/State/StateManager.cs index 576346efe7..685ea0b28c 100644 --- a/Source/Csla.Blazor/State/StateManager.cs +++ b/Source/Csla.Blazor/State/StateManager.cs @@ -39,14 +39,11 @@ public Task InitializeAsync(TimeSpan timeout) /// Get state from cache. /// /// Time to wait before timing out - private async Task GetState(TimeSpan timeout) + private async Task GetState(TimeSpan timeout) { - Session session = null; var isBrowser = OperatingSystem.IsBrowser(); if (isBrowser) - session = await _sessionManager.RetrieveSession(timeout); - - return session; + _ = await _sessionManager.RetrieveSession(timeout); } /// From 911eb96d0b634d8960c56d3f35e57b7558cc0fea Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Wed, 1 May 2024 17:47:21 -0300 Subject: [PATCH 03/10] Refactor SessionManagerTests and remove certain test methods This commit includes a significant refactoring of the `SessionManagerTests.cs` file. The `GetHttpClient` method has been simplified by removing the creation of a new `MemoryStream` instance and the use of `CslaBinaryWriter`. The stream's position is no longer reset to 0, and the array is directly obtained from the stream. Several test methods have been removed, including `RetrieveSession_WithTimeoutValue_ShouldNotThrowException`, `RetrieveSession_WithValidCancellationToken_ShouldNotThrowException`, and `SendSession_WithZeroTimeout_ShouldThrowTimeoutException`. These tests were checking for specific exceptions or session values. The `SendSession_WithTimeoutValue_ShouldNotThrowException` test method has been modified by removing the assertion that was previously checking if the operation is successful. Lastly, the `RetrieveCachedSessionSession` method has been modified by removing the call to `GetCachedSession` method of `_sessionManager`. --- .../Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index a30751b77d..0a938b3b99 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -125,11 +125,8 @@ private static HttpClient GetHttpClient(SessionMessage session, ApplicationConte private static byte[] GetSession(SessionMessage session, ApplicationContext _applicationContext) { var info = new MobileFormatter(_applicationContext).SerializeToDTO(session); - var ms = new MemoryStream(); - new CslaBinaryWriter(_applicationContext).Write(ms, info); - ms.Position = 0; var array = ms.ToArray(); return array; @@ -148,7 +145,6 @@ public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); - } [TestMethod] @@ -157,7 +153,6 @@ public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowExcep var cts = new CancellationTokenSource(); var session = await _sessionManager.RetrieveSession(cts.Token); Assert.AreEqual(session, SessionValue.Session); - } [TestMethod] @@ -173,7 +168,6 @@ public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); await _sessionManager.SendSession(timeout); - Assert.IsTrue(true); } @@ -182,7 +176,6 @@ public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); - } [TestMethod] @@ -205,7 +198,6 @@ public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTimeoutE [TestMethod] public void RetrieveCAchedSessionSession() { - _sessionManager.GetCachedSession(); } } From 2b3521943062d18fead86891b892b2f55f0d5f13 Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Thu, 2 May 2024 17:43:40 -0300 Subject: [PATCH 04/10] Refactor SessionManager and update related tests Updated `SessionManager.cs` methods `RetrieveSession` and `SendSession` to handle `TaskCanceledException` internally and rethrow as `TimeoutException`. Simplified `SendSession` by removing exception handling and refactored `RetrieveSession` to move `stateResult` handling outside of try-catch block. Renamed test methods in `SessionManagerTests.cs` to reflect these changes and updated expected exception type. --- .../SessionManagerTests.cs | 8 +-- .../State/SessionManager.cs | 67 +++++++++---------- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index 0a938b3b99..4ec715c838 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -156,11 +156,11 @@ public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowExcep } [TestMethod] - public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTimeoutException() + public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException() { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); } [TestMethod] @@ -187,11 +187,11 @@ public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException } [TestMethod] - public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTimeoutException() + public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException() { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); } diff --git a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs index a43f5494fd..e68aa8d938 100644 --- a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs +++ b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs @@ -46,9 +46,16 @@ public Session GetCachedSession() /// the web server to the wasm client /// if SyncContextWithServer is true. /// - public Task RetrieveSession(TimeSpan timeout) + public async Task RetrieveSession(TimeSpan timeout) { - return RetrieveSession(GetCancellationToken(timeout)); + try + { + return await RetrieveSession(GetCancellationToken(timeout)); + } + catch (TaskCanceledException tcex) + { + throw new TimeoutException($"{this.GetType().FullName}.{nameof(RetrieveSession)}.", tcex); + } } /// @@ -64,35 +71,25 @@ public async Task RetrieveSession(CancellationToken ct) if (_session != null) lastTouched = _session.LastTouched; var url = $"{_options.StateControllerName}?lastTouched={lastTouched}"; - - try + var stateResult = await client.GetFromJsonAsync(url, ct); + if (stateResult.ResultStatus == ResultStatuses.Success) { - var stateResult = await client.GetFromJsonAsync(url, ct); - - - if (stateResult.ResultStatus == ResultStatuses.Success) + var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); + var buffer = new MemoryStream(stateResult.SessionData) { - var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); - var buffer = new MemoryStream(stateResult.SessionData) - { - Position = 0 - }; - var message = (SessionMessage)formatter.Deserialize(buffer); - _session = message.Session; - if (message.Principal is not null && - ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) - { - provider.SetPrincipal(message.Principal); - } - } - else // NoUpdates + Position = 0 + }; + var message = (SessionMessage)formatter.Deserialize(buffer); + _session = message.Session; + if (message.Principal is not null && + ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) { - _session = GetSession(); + provider.SetPrincipal(message.Principal); } } - catch(OperationCanceledException ocex) + else // NoUpdates { - throw new TimeoutException ($"{this.GetType().FullName}.RetrieveSession.", ocex); + _session = GetSession(); } } else @@ -107,9 +104,16 @@ public async Task RetrieveSession(CancellationToken ct) /// the wasm client to the web server /// if SyncContextWithServer is true. /// - public Task SendSession(TimeSpan timeout) + public async Task SendSession(TimeSpan timeout) { - return SendSession(GetCancellationToken(timeout)); + try + { + await SendSession(GetCancellationToken(timeout)); + } + catch (TaskCanceledException tcex) + { + throw new TimeoutException($"{this.GetType().FullName}.{nameof(SendSession)}.", tcex); + } } private static CancellationToken GetCancellationToken(TimeSpan timeout) @@ -132,14 +136,7 @@ public async Task SendSession(CancellationToken ct) var buffer = new MemoryStream(); formatter.Serialize(buffer, _session); buffer.Position = 0; - try - { - await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); - } - catch(OperationCanceledException ocex) - { - throw new TimeoutException($"{this.GetType().FullName}.SendSession.", ocex); - } + await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); } } From 33a0c175f7069472b1506a9fc0c4d99192f249b7 Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Fri, 3 May 2024 18:59:15 -0300 Subject: [PATCH 05/10] `Convert SaveState to async in StateManager.cs` ` ` `The SaveState method in StateManager.cs has been converted from a synchronous method to an asynchronous one. This is indicated by the addition of the async keyword and the change in return type from void to Task. Additionally, the call to _sessionManager.SendSession(timeout) within the SaveState method has been updated to use the await keyword, making this method call awaited, in line with the change to make SaveState an asynchronous method.` --- Source/Csla.Blazor/State/StateManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Csla.Blazor/State/StateManager.cs b/Source/Csla.Blazor/State/StateManager.cs index 685ea0b28c..8631eeb92c 100644 --- a/Source/Csla.Blazor/State/StateManager.cs +++ b/Source/Csla.Blazor/State/StateManager.cs @@ -56,12 +56,12 @@ private async Task GetState(TimeSpan timeout) /// at which you know the user is navigating to another /// page. /// - public void SaveState(TimeSpan timeout) + public async Task SaveState(TimeSpan timeout) { var isBrowser = OperatingSystem.IsBrowser(); if (isBrowser) { - _sessionManager.SendSession(timeout); + await _sessionManager.SendSession(timeout); } } } From ce844449c5b3a7d98a4c0ecda951b023bb17cd1e Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Sat, 4 May 2024 12:07:32 -0300 Subject: [PATCH 06/10] Updated SessionManager and its tests for async operations and better error handling In this commit, several updates were made to the `SessionManager.cs` and `SessionManagerTests.cs` files. The variable `_sessionManager` was renamed to `_SessionManager` in `SessionManagerTests.cs`. The `Initialize` method was converted to an asynchronous method, and the `RetrieveSession` method call in it was updated to use `await`. XML comments were added to the `RetrieveSession`, `SendSession`, and `GetSession` methods in `SessionManager.cs` for better code documentation. The `RetrieveSession` and `SendSession` methods were updated to handle `TaskCanceledException` and throw a `TimeoutException` with a custom message. The `GetSession` method was updated to handle the case where `_session` is `null`, creating and returning a new `Session` object in this case. The `SendSession` method was updated to serialize the `_session` object and send it to the server if `SyncContextWithServer` is `true`. Finally, the `RetrieveSession` method was updated to retrieve the session from the server if `SyncContextWithServer` is `true`, deserializing and storing the retrieved session in `_session` or calling `GetSession` to get or create a new session if the retrieval is unsuccessful. --- .../SessionManagerTests.cs | 30 ++- .../State/SessionManager.cs | 215 +++++++++--------- 2 files changed, 125 insertions(+), 120 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index 4ec715c838..def4b1b213 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -21,11 +21,11 @@ namespace Csla.Test.State [TestClass] public class SessionManagerTests { - private SessionManager _sessionManager; + private SessionManager _SessionManager; private SessionMessage SessionValue; [TestInitialize] - public void Initialize() + public async Task Initialize() { var mockServiceProvider = new Mock(); @@ -58,8 +58,6 @@ public void Initialize() // Mock IServiceProvider mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Server.IDataPortalActivator))).Returns(mockActivator.Object); - // Mock IServiceProvider - // Mock IContextManager var mockContextManager = new Mock(); mockContextManager.Setup(x => x.IsValid).Returns(true); @@ -79,8 +77,8 @@ public void Initialize() var _applicationContext = new ApplicationContext(mockApplicationContextAccessor.Object); - _sessionManager = new SessionManager(_applicationContext, GetHttpClient(SessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); - _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); + _SessionManager = new SessionManager(_applicationContext, GetHttpClient(SessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); } private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext) @@ -97,8 +95,6 @@ private static HttpClient GetHttpClient(SessionMessage session, ApplicationConte // prepare the expected response of the mocked http call .ReturnsAsync((HttpRequestMessage request, CancellationToken cancellationToken) => { - - if (cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(cancellationToken); @@ -136,7 +132,7 @@ private static byte[] GetSession(SessionMessage session, ApplicationContext _app public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); - var session = await _sessionManager.RetrieveSession(timeout); + var session = await _SessionManager.RetrieveSession(timeout); Assert.AreEqual(session, SessionValue.Session); } @@ -144,14 +140,14 @@ public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException() public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _SessionManager.RetrieveSession(timeout)); } [TestMethod] public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowException() { var cts = new CancellationTokenSource(); - var session = await _sessionManager.RetrieveSession(cts.Token); + var session = await _SessionManager.RetrieveSession(cts.Token); Assert.AreEqual(session, SessionValue.Session); } @@ -160,14 +156,14 @@ public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTask { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _SessionManager.RetrieveSession(cts.Token)); } [TestMethod] public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); - await _sessionManager.SendSession(timeout); + await _SessionManager.SendSession(timeout); Assert.IsTrue(true); } @@ -175,14 +171,14 @@ public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(timeout)); } [TestMethod] public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException() { var cts = new CancellationTokenSource(); - await _sessionManager.SendSession(cts.Token); + await _SessionManager.SendSession(cts.Token); Assert.IsTrue(true); } @@ -191,14 +187,14 @@ public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanc { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(cts.Token)); } [TestMethod] public void RetrieveCAchedSessionSession() { - _sessionManager.GetCachedSession(); + _SessionManager.GetCachedSession(); } } } diff --git a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs index e68aa8d938..36458f3ab4 100644 --- a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs +++ b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs @@ -41,80 +41,86 @@ public Session GetCachedSession() return _session; } - /// - /// Retrieves the current user's session from - /// the web server to the wasm client - /// if SyncContextWithServer is true. - /// - public async Task RetrieveSession(TimeSpan timeout) - { - try - { - return await RetrieveSession(GetCancellationToken(timeout)); - } - catch (TaskCanceledException tcex) - { - throw new TimeoutException($"{this.GetType().FullName}.{nameof(RetrieveSession)}.", tcex); - } - } - - /// - /// Retrieves the current user's session from - /// the web server to the wasm client - /// if SyncContextWithServer is true. - /// - public async Task RetrieveSession(CancellationToken ct) - { - if (_options.SyncContextWithServer) - { - long lastTouched = 0; - if (_session != null) - lastTouched = _session.LastTouched; - var url = $"{_options.StateControllerName}?lastTouched={lastTouched}"; - var stateResult = await client.GetFromJsonAsync(url, ct); - if (stateResult.ResultStatus == ResultStatuses.Success) + /// + /// Retrieves the current user's session from + /// the web server to the wasm client + /// if SyncContextWithServer is true. + /// + /// The timeout duration for the operation. + /// The retrieved session. + public async Task RetrieveSession(TimeSpan timeout) { - var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); - var buffer = new MemoryStream(stateResult.SessionData) - { - Position = 0 - }; - var message = (SessionMessage)formatter.Deserialize(buffer); - _session = message.Session; - if (message.Principal is not null && - ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) - { - provider.SetPrincipal(message.Principal); - } + try + { + return await RetrieveSession(GetCancellationToken(timeout)); + } + catch (TaskCanceledException tcex) + { + throw new TimeoutException($"{this.GetType().FullName}.{nameof(RetrieveSession)}.", tcex); + } } - else // NoUpdates + + /// + /// Retrieves the current user's session from + /// the web server to the wasm client + /// if SyncContextWithServer is true. + /// + /// The cancellation token. + /// The retrieved session. + public async Task RetrieveSession(CancellationToken ct) { - _session = GetSession(); + if (_options.SyncContextWithServer) + { + long lastTouched = 0; + if (_session != null) + lastTouched = _session.LastTouched; + var url = $"{_options.StateControllerName}?lastTouched={lastTouched}"; + var stateResult = await client.GetFromJsonAsync(url, ct); + if (stateResult.ResultStatus == ResultStatuses.Success) + { + var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); + var buffer = new MemoryStream(stateResult.SessionData) + { + Position = 0 + }; + var message = (SessionMessage)formatter.Deserialize(buffer); + _session = message.Session; + if (message.Principal is not null && + ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) + { + provider.SetPrincipal(message.Principal); + } + } + else // NoUpdates + { + _session = GetSession(); + } + } + else + { + _session = GetSession(); + } + return _session; } - } - else - { - _session = GetSession(); - } - return _session; - } - /// - /// Sends the current user's session from - /// the wasm client to the web server - /// if SyncContextWithServer is true. - /// - public async Task SendSession(TimeSpan timeout) - { - try - { - await SendSession(GetCancellationToken(timeout)); - } - catch (TaskCanceledException tcex) - { - throw new TimeoutException($"{this.GetType().FullName}.{nameof(SendSession)}.", tcex); - } - } + /// + /// Sends the current user's session from + /// the wasm client to the web server + /// if SyncContextWithServer is true. + /// + /// The timeout duration for the operation. + /// A task representing the asynchronous operation. + public async Task SendSession(TimeSpan timeout) + { + try + { + await SendSession(GetCancellationToken(timeout)); + } + catch (TaskCanceledException tcex) + { + throw new TimeoutException($"{this.GetType().FullName}.{nameof(SendSession)}.", tcex); + } + } private static CancellationToken GetCancellationToken(TimeSpan timeout) { @@ -122,42 +128,45 @@ private static CancellationToken GetCancellationToken(TimeSpan timeout) return cts.Token; } - /// - /// Sends the current user's session from - /// the wasm client to the web server - /// if SyncContextWithServer is true. - /// - public async Task SendSession(CancellationToken ct) - { - _session.Touch(); - if (_options.SyncContextWithServer) - { - var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); - var buffer = new MemoryStream(); - formatter.Serialize(buffer, _session); - buffer.Position = 0; - await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); - } - } + /// + /// Sends the current user's session from + /// the wasm client to the web server + /// if SyncContextWithServer is true. + /// + /// The cancellation token. + /// A task representing the asynchronous operation. + public async Task SendSession(CancellationToken ct) + { + _session.Touch(); + if (_options.SyncContextWithServer) + { + var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); + var buffer = new MemoryStream(); + formatter.Serialize(buffer, _session); + buffer.Position = 0; + await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); + } + } - /// - /// Gets or creates the session data. - /// - private Session GetSession() - { - Session result; - if (_session != null) - { - result = _session; - } - else - { - result = []; - result.Touch(); - } - return result; - } + /// + /// Gets or creates the session data. + /// + /// The session data. + private Session GetSession() + { + Session result; + if (_session != null) + { + result = _session; + } + else + { + result = []; + result.Touch(); + } + return result; + } // server-side methods Session ISessionManager.GetSession() => throw new NotImplementedException(); From 73e9986a5c247ce6518f92e3d3a2e652e2ee10d5 Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Sat, 4 May 2024 12:17:23 -0300 Subject: [PATCH 07/10] Subject: Refactored test methods and updated session retrieval Refactored the `Initialize` method in `SessionManagerTests.cs` to be synchronous and removed the `RetrieveSession` call. The `RetrieveSession` call has been added to four test methods to ensure session retrieval before each test. Renamed and converted `RetrieveCAchedSessionSession` to an asynchronous method, adding a `RetrieveSession` call and an assertion for non-null cached sessions. Added a new test method `RetrieveNullCachedSessionSession` to assert null cached sessions. --- .../SessionManagerTests.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index def4b1b213..12dcc03c44 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -25,7 +25,7 @@ public class SessionManagerTests private SessionMessage SessionValue; [TestInitialize] - public async Task Initialize() + public void Initialize() { var mockServiceProvider = new Mock(); @@ -78,7 +78,6 @@ public async Task Initialize() _SessionManager = new SessionManager(_applicationContext, GetHttpClient(SessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); } private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext) @@ -162,6 +161,8 @@ public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTask [TestMethod] public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() { + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + var timeout = TimeSpan.FromHours(1); await _SessionManager.SendSession(timeout); Assert.IsTrue(true); @@ -170,6 +171,8 @@ public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() [TestMethod] public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() { + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + var timeout = TimeSpan.Zero; await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(timeout)); } @@ -177,6 +180,8 @@ public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() [TestMethod] public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException() { + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + var cts = new CancellationTokenSource(); await _SessionManager.SendSession(cts.Token); Assert.IsTrue(true); @@ -185,6 +190,8 @@ public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException [TestMethod] public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException() { + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + var cts = new CancellationTokenSource(); cts.Cancel(); await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(cts.Token)); @@ -192,9 +199,18 @@ public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanc [TestMethod] - public void RetrieveCAchedSessionSession() + public async Task RetrieveCachedSessionSession() + { + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + + var session = _SessionManager.GetCachedSession(); + Assert.IsNotNull(session); + } + [TestMethod] + public void RetrieveNullCachedSessionSession() { - _SessionManager.GetCachedSession(); + var session = _SessionManager.GetCachedSession(); + Assert.IsNull(session); } } } From 5f59d4a50f8c6d9f6827520d87caf8df176f4d1e Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Sat, 4 May 2024 18:09:10 -0300 Subject: [PATCH 08/10] Refactor variable names to follow C# naming convention Updated variable names `_SessionManager` and `SessionValue` to `_sessionManager` and `_sessionValue` respectively, to adhere to the common C# naming convention for private fields. All instances of these variables in the code, including in the `Initialize()`, `Deserialize()`, `RetrieveSession()`, `SendSession()`, and `GetCachedSession()` methods, as well as in test assertions, have been updated accordingly. --- .../SessionManagerTests.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index 12dcc03c44..81ff7ab43f 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -21,8 +21,8 @@ namespace Csla.Test.State [TestClass] public class SessionManagerTests { - private SessionManager _SessionManager; - private SessionMessage SessionValue; + private SessionManager _sessionManager; + private SessionMessage _sessionValue; [TestInitialize] public void Initialize() @@ -35,7 +35,7 @@ public void Initialize() // Mock IServiceProvider mockServiceProvider.Setup(x => x.GetService(typeof(AuthenticationStateProvider))).Returns(mockAuthStateProvider.Object); - SessionValue = new SessionMessage + _sessionValue = new SessionMessage { // Set properties here // For example: @@ -46,7 +46,7 @@ public void Initialize() // Mock ISerializationFormatter var mockFormatter = new Mock(); mockFormatter.Setup(x => x.Serialize(It.IsAny(), It.IsAny())); - mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(SessionValue); + mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(_sessionValue); // Mock IServiceProvider mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter.Object); @@ -77,7 +77,7 @@ public void Initialize() var _applicationContext = new ApplicationContext(mockApplicationContextAccessor.Object); - _SessionManager = new SessionManager(_applicationContext, GetHttpClient(SessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); + _sessionManager = new SessionManager(_applicationContext, GetHttpClient(_sessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); } private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext) @@ -131,23 +131,23 @@ private static byte[] GetSession(SessionMessage session, ApplicationContext _app public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); - var session = await _SessionManager.RetrieveSession(timeout); - Assert.AreEqual(session, SessionValue.Session); + var session = await _sessionManager.RetrieveSession(timeout); + Assert.AreEqual(session, _sessionValue.Session); } [TestMethod] public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _SessionManager.RetrieveSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); } [TestMethod] public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowException() { var cts = new CancellationTokenSource(); - var session = await _SessionManager.RetrieveSession(cts.Token); - Assert.AreEqual(session, SessionValue.Session); + var session = await _sessionManager.RetrieveSession(cts.Token); + Assert.AreEqual(session, _sessionValue.Session); } [TestMethod] @@ -155,61 +155,61 @@ public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTask { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _SessionManager.RetrieveSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); } [TestMethod] public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() { - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + await _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); var timeout = TimeSpan.FromHours(1); - await _SessionManager.SendSession(timeout); + await _sessionManager.SendSession(timeout); Assert.IsTrue(true); } [TestMethod] public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() { - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + await _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); } [TestMethod] public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException() { - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + await _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); var cts = new CancellationTokenSource(); - await _SessionManager.SendSession(cts.Token); + await _sessionManager.SendSession(cts.Token); Assert.IsTrue(true); } [TestMethod] public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException() { - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + await _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); } [TestMethod] public async Task RetrieveCachedSessionSession() { - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + await _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); - var session = _SessionManager.GetCachedSession(); + var session = _sessionManager.GetCachedSession(); Assert.IsNotNull(session); } [TestMethod] public void RetrieveNullCachedSessionSession() { - var session = _SessionManager.GetCachedSession(); + var session = _sessionManager.GetCachedSession(); Assert.IsNull(session); } } From b6c01b9ffac17e7fc3e80b1a95b44a5cee34aa7c Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Tue, 7 May 2024 17:31:29 -0300 Subject: [PATCH 09/10] Switch from Moq to NSubstitute in Csla.Blazor.WebAssembly.Tests This commit represents a significant shift in the mocking framework used for unit testing in the `Csla.Blazor.WebAssembly.Tests.csproj` project. The `Moq` package has been replaced with `NSubstitute` in the project file and throughout the `SessionManagerTests.cs` file. This includes changes in the way mocks are created, set up, and how return values are specified for mocked methods and properties. Additionally, a new `TestHttpMessageHandler` class has been added to `SessionManagerTests.cs` to mock the behavior of an `HttpClient`. The `GetHttpClient` method has been updated to use this new class, aligning with the switch from `Moq` to `NSubstitute`. --- .../Csla.Blazor.WebAssembly.Tests.csproj | 2 +- .../SessionManagerTests.cs | 83 ++++++++----------- 2 files changed, 35 insertions(+), 50 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj b/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj index 107042eeeb..8af20c9d4a 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj +++ b/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj @@ -12,13 +12,13 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index 81ff7ab43f..8788766da0 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -4,17 +4,16 @@ using System.Threading.Tasks; using Csla.Blazor.WebAssembly.State; using Csla.State; -using Moq; using System.Net.Http; using Csla.Blazor.WebAssembly.Configuration; using Csla.Core; using Csla.Runtime; -using Moq.Protected; using System.Net; using Csla.Serialization; using System.Runtime.Serialization.Formatters.Binary; using Csla.Serialization.Mobile; using Microsoft.AspNetCore.Components.Authorization; +using NSubstitute; namespace Csla.Test.State { @@ -27,13 +26,13 @@ public class SessionManagerTests [TestInitialize] public void Initialize() { - var mockServiceProvider = new Mock(); + var mockServiceProvider = Substitute.For(); // Mock AuthenticationStateProvider - var mockAuthStateProvider = new Mock(); + var mockAuthStateProvider = Substitute.For(); // Mock IServiceProvider - mockServiceProvider.Setup(x => x.GetService(typeof(AuthenticationStateProvider))).Returns(mockAuthStateProvider.Object); + mockServiceProvider.GetService(typeof(AuthenticationStateProvider)).Returns(mockAuthStateProvider); _sessionValue = new SessionMessage { @@ -44,73 +43,59 @@ public void Initialize() }; // Mock ISerializationFormatter - var mockFormatter = new Mock(); - mockFormatter.Setup(x => x.Serialize(It.IsAny(), It.IsAny())); - mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(_sessionValue); + var mockFormatter = Substitute.For(); + mockFormatter.Serialize(Arg.Any(), Arg.Any()); + mockFormatter.Deserialize(Arg.Any()).Returns(_sessionValue); // Mock IServiceProvider - mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter.Object); + mockServiceProvider.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter)).Returns(mockFormatter); - var mockActivator = new Mock(); - mockActivator.Setup(x => x.CreateInstance(It.Is(t => t == typeof(Csla.Serialization.Mobile.MobileFormatter)))).Returns(mockFormatter.Object); - mockActivator.Setup(x => x.InitializeInstance(It.IsAny())); + var mockActivator = Substitute.For(); + mockActivator.CreateInstance(Arg.Is(t => t == typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter); + mockActivator.InitializeInstance(Arg.Any()); // Mock IServiceProvider - mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Server.IDataPortalActivator))).Returns(mockActivator.Object); + mockServiceProvider.GetService(typeof(Csla.Server.IDataPortalActivator)).Returns(mockActivator); // Mock IContextManager - var mockContextManager = new Mock(); - mockContextManager.Setup(x => x.IsValid).Returns(true); + var mockContextManager = Substitute.For(); + mockContextManager.IsValid.Returns(true); // Mock IContextManagerLocal - var mockLocalContextManager = new Mock(); + var mockLocalContextManager = Substitute.For(); // Mock IServiceProvider - mockServiceProvider.Setup(x => x.GetService(typeof(IRuntimeInfo))).Returns(new RuntimeInfo()); + mockServiceProvider.GetService(typeof(IRuntimeInfo)).Returns(new RuntimeInfo()); // Mock IEnumerable - var mockContextManagerList = new List { mockContextManager.Object }; + var mockContextManagerList = new List { mockContextManager }; // Mock ApplicationContextAccessor - var mockApplicationContextAccessor = new Mock(mockContextManagerList, mockLocalContextManager.Object, mockServiceProvider.Object); - - var _applicationContext = new ApplicationContext(mockApplicationContextAccessor.Object); + var mockApplicationContextAccessor = Substitute.For(mockContextManagerList, mockLocalContextManager, mockServiceProvider); + var _applicationContext = new ApplicationContext(mockApplicationContextAccessor); _sessionManager = new SessionManager(_applicationContext, GetHttpClient(_sessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); } + public class TestHttpMessageHandler(SessionMessage session, ApplicationContext _applicationContext) : HttpMessageHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var response = new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{\"ResultStatus\":0, \"SessionData\":\"" + Convert.ToBase64String(GetSession(session, _applicationContext)) + "\"}"), + }; + return Task.FromResult(response); + } + } private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext) { - var handlerMock = new Mock(MockBehavior.Strict); - handlerMock - .Protected() - // Setup the PROTECTED method to mock - .Setup>( - "SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny() - ) - // prepare the expected response of the mocked http call - .ReturnsAsync((HttpRequestMessage request, CancellationToken cancellationToken) => - { - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException(cancellationToken); - } - else - { - return new HttpResponseMessage() - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("{\"ResultStatus\":0, \"SessionData\":\"" + Convert.ToBase64String(GetSession(session, _applicationContext)) + "\"}"), - }; - } - }) - .Verifiable(); - + var handlerMock = new TestHttpMessageHandler(session,_applicationContext); // use real http client with mocked handler here - var httpClient = new HttpClient(handlerMock.Object) + var httpClient = new HttpClient(handlerMock) { BaseAddress = new Uri("http://test.com/"), }; From 97ddc29d9368312769232ff41615b9b8a664d031 Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Sun, 19 May 2024 11:15:15 -0300 Subject: [PATCH 10/10] Refactored code for readability and modified deserialization process Removed `OnDeserialized` method and related code from several classes, indicating a change in the deserialization process. Reordered using directives and added spaces around equals signs and commas for better readability. Removed unnecessary curly braces and adjusted spaces around curly braces in various methods. Removed decrement of `EditLevel` in `AcceptChanges` method and changed conditions in `Child_PropertyChanged` method. Added `System.Collections.Concurrent` namespace in `ReadOnlyBase.cs`. --- Source/Csla/BusinessBindingListBase.cs | 29 +-------- Source/Csla/BusinessListBase.cs | 25 +------- Source/Csla/Core/ExtendedBindingList.cs | 23 +------- Source/Csla/Core/ManagedObjectBase.cs | 36 +++--------- Source/Csla/Core/ObservableBindingList.cs | 29 ++------- Source/Csla/DynamicBindingListBase.cs | 50 +++++----------- Source/Csla/DynamicListBase.cs | 20 +------ Source/Csla/ReadOnlyBase.cs | 71 ++++++++++------------- 8 files changed, 63 insertions(+), 220 deletions(-) diff --git a/Source/Csla/BusinessBindingListBase.cs b/Source/Csla/BusinessBindingListBase.cs index e2ec912f44..c4e76ef877 100644 --- a/Source/Csla/BusinessBindingListBase.cs +++ b/Source/Csla/BusinessBindingListBase.cs @@ -851,33 +851,6 @@ bool Core.ITrackStatus.IsDeleted #endregion - #region Serialization Notification - - [NonSerialized] - [NotUndoable] - private bool _deserialized = false; - - /// - /// This method is called on a newly deserialized object - /// after deserialization is complete. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - protected override void OnDeserialized() - { - _deserialized = true; - base.OnDeserialized(); - - foreach (Core.IEditableBusinessObject child in this) - { - child.SetParent(this); - } - - foreach (Core.IEditableBusinessObject child in DeletedList) - child.SetParent(this); - } - - #endregion - #region Child Data Access /// @@ -1351,7 +1324,7 @@ void IDataPortalTarget.Child_OnDataPortalException(DataPortalEventArgs e, Except [EditorBrowsable(EditorBrowsableState.Never)] protected override void Child_PropertyChanged(object sender, PropertyChangedEventArgs e) { - if (_deserialized && RaiseListChangedEvents && e != null) + if (RaiseListChangedEvents && e != null) { for (int index = 0; index < Count; index++) { diff --git a/Source/Csla/BusinessListBase.cs b/Source/Csla/BusinessListBase.cs index 8b1a1cf8f3..de9d27a29f 100644 --- a/Source/Csla/BusinessListBase.cs +++ b/Source/Csla/BusinessListBase.cs @@ -6,11 +6,11 @@ // This is the base class from which most business collections //----------------------------------------------------------------------- +using System.Collections.Specialized; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using Csla.Core; using Csla.Properties; -using System.Collections.Specialized; using Csla.Serialization.Mobile; using Csla.Server; @@ -598,7 +598,6 @@ private void AcceptChanges(int parentEditLevel) if (child.EditLevelAdded > EditLevel) DeletedList.RemoveAt(index); } - if (EditLevel < 0) EditLevel = 0; } @@ -861,24 +860,6 @@ bool Core.ITrackStatus.IsDeleted #endregion - #region Serialization Notification - - /// - /// Reset parent references on deserialization. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - protected override void OnDeserialized() - { - base.OnDeserialized(); - foreach (Core.IEditableBusinessObject child in this) - child.SetParent(this); - - foreach (Core.IEditableBusinessObject child in DeletedList) - child.SetParent(this); - } - - #endregion - #region Child Data Access /// @@ -1203,7 +1184,7 @@ protected virtual void OnSaved(T newObject, Exception e, object userState) /// This value will be Nothing for root objects. /// [Browsable(false)] - [Display(AutoGenerateField=false)] + [Display(AutoGenerateField = false)] [System.ComponentModel.DataAnnotations.ScaffoldColumn(false)] [EditorBrowsable(EditorBrowsableState.Advanced)] public Core.IParent Parent @@ -1237,7 +1218,7 @@ protected virtual void SetParent(Core.IParent parent) _parent = parent; _identityManager = null; } - else + else { // case when current identity manager has next identity of > 1 // parent next identity incremented by 1 not accounting for this (child collection) next identity diff --git a/Source/Csla/Core/ExtendedBindingList.cs b/Source/Csla/Core/ExtendedBindingList.cs index 1e495a886c..1cd29be11b 100644 --- a/Source/Csla/Core/ExtendedBindingList.cs +++ b/Source/Csla/Core/ExtendedBindingList.cs @@ -249,32 +249,11 @@ protected virtual void OnRemoveEventHooks(T item) child.ChildChanged -= Child_Changed; } - /// - /// This method is called on a newly deserialized object - /// after deserialization is complete. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - protected virtual void OnDeserialized() - { - // do nothing - this is here so a subclass - // could override if needed - } - void ISerializationNotification.Deserialized() { // don't rehook events here, because the MobileFormatter has // created new objects and so the lists will auto-subscribe - // the events - OnDeserialized(); - } - - [System.Runtime.Serialization.OnDeserialized] - private void OnDeserializedHandler(System.Runtime.Serialization.StreamingContext context) - { - foreach (T item in this) - OnAddEventHooks(item); - - OnDeserialized(); + // the events; } [NonSerialized] diff --git a/Source/Csla/Core/ManagedObjectBase.cs b/Source/Csla/Core/ManagedObjectBase.cs index 99025e26b5..5358478488 100644 --- a/Source/Csla/Core/ManagedObjectBase.cs +++ b/Source/Csla/Core/ManagedObjectBase.cs @@ -6,10 +6,10 @@ // Base class for an object that is serializable //----------------------------------------------------------------------- +using System.ComponentModel; using System.Linq.Expressions; using System.Reflection; using Csla.Core.FieldManager; -using System.ComponentModel; using Csla.Reflection; using Csla.Serialization.Mobile; @@ -21,9 +21,9 @@ namespace Csla.Core /// [Serializable] public abstract class ManagedObjectBase : MobileObject, - INotifyPropertyChanged, + INotifyPropertyChanged, IManageProperties, - IUseApplicationContext, + IUseApplicationContext, IUseFieldManager { /// @@ -85,7 +85,7 @@ protected static PropertyInfo

RegisterProperty

(Type objectType, PropertyIn /// Type of property /// Property Expression /// The provided IPropertyInfo object. - protected static PropertyInfo

RegisterProperty(Expression> propertyLambdaExpression) + protected static PropertyInfo

RegisterProperty(Expression> propertyLambdaExpression) { PropertyInfo reflectedPropertyInfo = Reflect.GetProperty(propertyLambdaExpression); @@ -116,7 +116,7 @@ protected static PropertyInfo

RegisterProperty(ExpressionProperty Expression /// Friendly description for a property to be used in databinding /// The provided IPropertyInfo object. - protected static PropertyInfo

RegisterProperty(Expression> propertyLambdaExpression, string friendlyName) + protected static PropertyInfo

RegisterProperty(Expression> propertyLambdaExpression, string friendlyName) { PropertyInfo reflectedPropertyInfo = Reflect.GetProperty(propertyLambdaExpression); @@ -132,7 +132,7 @@ protected static PropertyInfo

RegisterProperty(ExpressionProperty Expression /// Friendly description for a property to be used in databinding /// Default Value for the property - protected static PropertyInfo

RegisterProperty(Expression> propertyLambdaExpression, string friendlyName, P defaultValue) + protected static PropertyInfo

RegisterProperty(Expression> propertyLambdaExpression, string friendlyName, P defaultValue) { PropertyInfo reflectedPropertyInfo = Reflect.GetProperty(propertyLambdaExpression); @@ -384,7 +384,7 @@ protected virtual bool LoadPropertyMarkDirty(IPropertyInfo propertyInfo, object var method = t.GetMethods(flags).FirstOrDefault(c => c.Name == "LoadPropertyMarkDirty" && c.IsGenericMethod); var gm = method.MakeGenericMethod(propertyInfo.Type); var p = new object[] { propertyInfo, newValue }; - return (bool) gm.Invoke(this, p); + return (bool)gm.Invoke(this, p); } #endregion @@ -459,28 +459,6 @@ protected override void OnSetChildren(SerializationInfo info, MobileFormatter fo #endregion - #region OnDeserialized - - - [System.Runtime.Serialization.OnDeserialized] - private void OnDeserializedHandler(System.Runtime.Serialization.StreamingContext context) - { - OnDeserialized(context); - } - - ///

- /// This method is called on a newly deserialized object - /// after deserialization is complete. - /// - /// Serialization context object. - [EditorBrowsable(EditorBrowsableState.Advanced)] - protected virtual void OnDeserialized(System.Runtime.Serialization.StreamingContext context) - { - FieldManager?.SetPropertyList(this.GetType()); - } - - #endregion - bool IManageProperties.FieldExists(IPropertyInfo property) => FieldManager.FieldExists(property); List IManageProperties.GetManagedProperties() => FieldManager.GetRegisteredProperties(); object IManageProperties.GetProperty(IPropertyInfo propertyInfo) => throw new NotImplementedException(); diff --git a/Source/Csla/Core/ObservableBindingList.cs b/Source/Csla/Core/ObservableBindingList.cs index 69a07167fb..564d9bef63 100644 --- a/Source/Csla/Core/ObservableBindingList.cs +++ b/Source/Csla/Core/ObservableBindingList.cs @@ -6,11 +6,11 @@ // Extends ObservableCollection with behaviors required //----------------------------------------------------------------------- -using System.ComponentModel; -using Csla.Serialization.Mobile; using System.Collections.Specialized; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using Csla.Properties; +using Csla.Serialization.Mobile; namespace Csla.Core { @@ -77,7 +77,7 @@ public bool AllowRemove ///
public bool RaiseListChangedEvents { - get { return _raiseListChangedEvents; } + get { return _raiseListChangedEvents; } set { _raiseListChangedEvents = value; } } @@ -344,32 +344,11 @@ protected virtual void OnRemoveEventHooks(T item) #region ISerializationNotification Members - /// - /// This method is called on a newly deserialized object - /// after deserialization is complete. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - protected virtual void OnDeserialized() - { - // do nothing - this is here so a subclass - // could override if needed - } - - [System.Runtime.Serialization.OnDeserialized] - private void OnDeserializedHandler(System.Runtime.Serialization.StreamingContext context) - { - foreach (T item in this) - OnAddEventHooks(item); - - OnDeserialized(); - } - void ISerializationNotification.Deserialized() { // don't rehook events here, because the MobileFormatter has // created new objects and so the lists will auto-subscribe // the events - OnDeserialized(); } #endregion @@ -622,7 +601,7 @@ protected override void SetLoadListMode(bool enabled) else { if (_oldRLCE.Count > 0) - _raiseListChangedEvents = _oldRLCE.Pop(); + _raiseListChangedEvents = _oldRLCE.Pop(); } } } diff --git a/Source/Csla/DynamicBindingListBase.cs b/Source/Csla/DynamicBindingListBase.cs index 953c4e0578..49926a48bf 100644 --- a/Source/Csla/DynamicBindingListBase.cs +++ b/Source/Csla/DynamicBindingListBase.cs @@ -45,7 +45,7 @@ public abstract class DynamicBindingListBase : IBusinessObject, IUseApplicationContext where T : Core.IEditableBusinessObject, Core.IUndoableObject, Core.ISavable, IMobileObject, IBusinessObject - { + { /// /// Creates an instance of the type. /// @@ -202,9 +202,9 @@ public virtual T SaveItem(int index) return result; } -#endregion + #endregion -#region Saved Event + #region Saved Event [NonSerialized] [NotUndoable] private EventHandler _nonSerializableSavedHandlers; @@ -258,9 +258,9 @@ protected virtual void OnSaved(T newObject, Exception e) _serializableSavedHandlers?.Invoke(this, args); } -#endregion + #endregion -#region Insert, Remove, Clear + #region Insert, Remove, Clear /// /// Adds a new item to the list. @@ -326,9 +326,9 @@ protected override void SetItem(int index, T item) base.SetItem(index, item); } -#endregion + #endregion -#region IParent Members + #region IParent Members void Csla.Core.IParent.ApplyEditChild(Core.IEditableBusinessObject child) { @@ -347,9 +347,9 @@ IParent Csla.Core.IParent.Parent get { return null; } } -#endregion + #endregion -#region Cascade Child events + #region Cascade Child events /// /// Handles any PropertyChanged event from @@ -414,29 +414,9 @@ private PropertyDescriptor GetPropertyDescriptor(string propertyName) return result; } -#endregion - -#region Serialization Notification - - /// - /// This method is called on a newly deserialized object - /// after deserialization is complete. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - protected override void OnDeserialized() - { - foreach (IEditableBusinessObject child in this) - { - child.SetParent(this); - if (child is INotifyPropertyChanged c) - c.PropertyChanged += Child_PropertyChanged; - } - base.OnDeserialized(); - } - -#endregion + #endregion -#region Data Access + #region Data Access private void DataPortal_Update() { @@ -484,9 +464,9 @@ protected virtual void DataPortal_OnDataPortalException(DataPortalEventArgs e, E } -#endregion + #endregion -#region ToArray + #region ToArray /// /// Get an array containing all items in the list. @@ -499,9 +479,9 @@ public T[] ToArray() return result.ToArray(); } -#endregion + #endregion -#region IDataPortalTarget Members + #region IDataPortalTarget Members void Csla.Server.IDataPortalTarget.CheckRules() { } diff --git a/Source/Csla/DynamicListBase.cs b/Source/Csla/DynamicListBase.cs index 650630af46..3e9b9b5ca5 100644 --- a/Source/Csla/DynamicListBase.cs +++ b/Source/Csla/DynamicListBase.cs @@ -425,9 +425,9 @@ void Csla.Core.IParent.RemoveChild(Core.IEditableBusinessObject child) } - IParent Csla.Core.IParent.Parent + IParent Csla.Core.IParent.Parent { - get { return null; } + get { return null; } } #endregion @@ -475,22 +475,6 @@ public override bool IsBusy } #endregion - #region Serialization Notification - - /// - /// Set parent reference. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - protected override void OnDeserialized() - { - foreach (IEditableBusinessObject child in this) - child.SetParent(this); - - base.OnDeserialized(); - } - - #endregion - #region Data Access private void DataPortal_Update() diff --git a/Source/Csla/ReadOnlyBase.cs b/Source/Csla/ReadOnlyBase.cs index 60c634fb3b..4a82130e49 100644 --- a/Source/Csla/ReadOnlyBase.cs +++ b/Source/Csla/ReadOnlyBase.cs @@ -6,6 +6,7 @@ // This is a base class from which readonly business classes //----------------------------------------------------------------------- +using System.Collections.Concurrent; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics; @@ -19,7 +20,6 @@ using Csla.Rules; using Csla.Serialization.Mobile; using Csla.Server; -using System.Collections.Concurrent; namespace Csla { @@ -78,8 +78,8 @@ public override string ToString() /// Gets or sets the current ApplicationContext object. /// protected ApplicationContext ApplicationContext { get; set; } - ApplicationContext IUseApplicationContext.ApplicationContext - { + ApplicationContext IUseApplicationContext.ApplicationContext + { get => ApplicationContext; set { @@ -211,7 +211,8 @@ protected virtual void AddBusinessRules() [EditorBrowsable(EditorBrowsableState.Advanced)] public virtual bool CanReadProperty(Csla.Core.IPropertyInfo property) { - if (IsCanReadPropertyAuthorizationCheckDisabled) { + if (IsCanReadPropertyAuthorizationCheckDisabled) + { return true; } @@ -374,9 +375,9 @@ private bool CanExecuteMethod(string methodName, bool throwOnFalse) return result; } -#endregion + #endregion -#region IClonable + #region IClonable object ICloneable.Clone() { @@ -502,9 +503,9 @@ protected virtual void Child_OnDataPortalException(DataPortalEventArgs e, Except { } -#endregion + #endregion -#region Serialization Notification + #region Serialization Notification void ISerializationNotification.Deserialized() { @@ -517,24 +518,12 @@ private void OnDeserializedHandler(System.Runtime.Serialization.StreamingContext if (_fieldManager != null) FieldManager.SetPropertyList(this.GetType()); InitializeBusinessRules(); - OnDeserialized(context); } - /// - /// This method is called on a newly deserialized object - /// after deserialization is complete. - /// - /// Serialization context object. - [EditorBrowsable(EditorBrowsableState.Advanced)] - protected virtual void OnDeserialized(System.Runtime.Serialization.StreamingContext context) - { - // do nothing - this is here so a subclass - // could override if needed - } -#endregion + #endregion -#region Register Properties + #region Register Properties /// /// Indicates that the specified property belongs @@ -767,9 +756,9 @@ protected static MethodInfo RegisterMethod(Expression> methodLambdaExp return RegisterMethod(reflectedMethodInfo.Name); } -#endregion + #endregion -#region Get Properties + #region Get Properties /// /// Gets a property's value, first checking authorization. @@ -812,14 +801,14 @@ protected P GetProperty

(string propertyName, P field, P defaultValue) /// user is not authorized to read this property. protected P GetProperty

(string propertyName, P field, P defaultValue, Security.NoAccessBehavior noAccess) { -#region Check to see if the property is marked with RelationshipTypes.PrivateField + #region Check to see if the property is marked with RelationshipTypes.PrivateField var propertyInfo = FieldManager.GetRegisteredProperty(propertyName); if ((propertyInfo.RelationshipType & RelationshipTypes.PrivateField) != RelationshipTypes.PrivateField) throw new InvalidOperationException(Resources.PrivateFieldException); -#endregion + #endregion if (CanReadProperty(propertyInfo, noAccess == Csla.Security.NoAccessBehavior.ThrowException)) return field; @@ -1106,9 +1095,9 @@ protected object GetProperty(IPropertyInfo propertyInfo) return result; } -#endregion + #endregion -#region Read Properties + #region Read Properties ///

/// Gets a property's value from the list of @@ -1235,9 +1224,9 @@ P IManageProperties.LazyReadPropertyAsync

(PropertyInfo

propertyInfo, Task< return LazyReadPropertyAsync(propertyInfo, factory); } -#endregion + #endregion -#region Load Properties + #region Load Properties ///

/// Loads a property's managed field with the @@ -1434,9 +1423,9 @@ protected void LoadPropertyAsync(PropertyInfo property, Task factory) { LoadManager.BeginLoad(new TaskLoader(property, factory)); } -#endregion + #endregion -#region Field Manager + #region Field Manager [NotUndoable] private FieldDataManager _fieldManager; @@ -1579,9 +1568,9 @@ protected virtual void OnBusyChanged(BusyChangedEventArgs args) _propertyBusy?.Invoke(this, args); } -#endregion + #endregion -#region IDataPortalTarget Members + #region IDataPortalTarget Members void Csla.Server.IDataPortalTarget.CheckRules() { } @@ -1627,9 +1616,9 @@ void Csla.Server.IDataPortalTarget.Child_OnDataPortalException(DataPortalEventAr this.Child_OnDataPortalException(e, ex); } -#endregion + #endregion -#region IManageProperties Members + #region IManageProperties Members bool IManageProperties.HasManagedProperties { @@ -1686,9 +1675,9 @@ List IManageProperties.GetChildren() { return FieldManager.GetChildren(); } -#endregion + #endregion -#region MobileFormatter + #region MobileFormatter /// /// Override this method to insert your child object @@ -1732,9 +1721,9 @@ protected override void OnSetChildren(Csla.Serialization.Mobile.SerializationInf base.OnSetChildren(info, formatter); } -#endregion + #endregion -#region INotifyUnhandledAsyncException Members + #region INotifyUnhandledAsyncException Members [NotUndoable] [NonSerialized] @@ -1772,6 +1761,6 @@ protected void OnUnhandledAsyncException(object originalSender, Exception error) OnUnhandledAsyncException(new Csla.Core.ErrorEventArgs(originalSender, error)); } -#endregion + #endregion } } \ No newline at end of file