Skip to content

Commit

Permalink
Add custom serializers to MobileFormatter (#4024)
Browse files Browse the repository at this point in the history
* #2531 Initial changes to implement IMobileSerializer concept

* #2531 Maintain Serialization configuration for custom formatters

* Use modern namespace technique

* Fix comment

* #2531 Initial implementation of deserialization

* Resolve build warning

* #2531 Rework how mobile formatter config works

* #2531 Custom serializer now works with ClaimsPrincipal

* #2531 Remove obsolete CslaClaimsPrincipal type

* Clean up ctor and application context

* #2531 Put ClaimsPrincipalSerializer into core framework

* #2531 Add custom serializer tests

* #2531 Updates based on PR feedback

* Fix comments

* #2531 Move serialzation formatter config to be instance not static

* Clean up what were static methods to work correctly now

* #2531 Allow unknown types to fall through FieldDataManager as "children"

* #2531 Ensure native types are handled correctly

* #2531 Update tests

* #2531 Use a func to determine if a type can be handled by a serializer

* #2531 Add test using CanSerialize

* #2531 Rework TypeMap to use generics for type safety and readability

* #3363 Add test for DateTime.Kind serialization

* #1877 Add implementation of ClaimsPrincipal serialization for .NET Framework

* Fix build warning

* #2148 Add Json-based PocoSerializer for simple types

* #2531 Update method name

* #2531 Add and use MobileFormatterException

* #2531 Update implementation and property scopes

* #2531 Make type internal

* Update tests

* #2531 Use DI to get the ISerializationFormatter service

* #2531 Remove SerializationFormatterFactory type

* #2531 Remove OriginalType

* Remove commented code

* #2531 Clean up code

* #2531 Fix expected exception type

* Remove obsolete compiler conditionals

* #3871 Updated release notes

* Add `dev/` prefix to working branch names

* #2531 Code cleanup and improvement based on PR review

* #2531 Make MobileFormatterOptions a service
  • Loading branch information
rockfordlhotka authored Jun 20, 2024
1 parent 018f366 commit 1f16f6b
Show file tree
Hide file tree
Showing 49 changed files with 1,044 additions and 654 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The rules are detailed below.
* If an issue doesn't exist, engage with the dev team via [Discussions](https://github.com/MarimerLLC/csla/discussions) or [Discord](https://discord.gg/9ahKjb7ccf) to discuss your concern
* Create a fork of the MarimerLLC/csla repo
* In your fork, create a topic/feature/issue branch from where you want to base your work
* We recommend naming your branch using the format `issueNumber-test` such as `1234-enhance-dataportal`
* We recommend naming your branch using the format `dev/issueNumber-test` such as `dev/1234-enhance-dataportal`
* Assign the issue
* If you have permissions, assign the Issue to yourself
* If you don't have permissions, **make a comment** in the issue saying that you are working on the issue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public System.Security.Principal.IPrincipal GetUser()
var result = HttpContext?.User;
if (result == null)
{
result = new Security.CslaClaimsPrincipal();
result = new ClaimsPrincipal();
SetUser(result);
}
return result;
Expand Down
8 changes: 4 additions & 4 deletions Source/Csla.AspNetCore/Blazor/State/StateController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Csla.State;
using Csla.Security;
using Csla.Blazor.State.Messages;
using Csla.Serialization;

namespace Csla.AspNetCore.Blazor.State
{
Expand Down Expand Up @@ -48,10 +49,9 @@ public virtual StateResult Get(long lastTouched)
message.Session = session;
if (FlowUserIdentityToWebAssembly)
{
var principal = new CslaClaimsPrincipal(applicationContext.Principal);
message.Principal = principal;
message.Principal = applicationContext.Principal;
}
var formatter = Serialization.SerializationFormatterFactory.GetFormatter(applicationContext);
var formatter = applicationContext.GetRequiredService<ISerializationFormatter>();
var buffer = new MemoryStream();
formatter.Serialize(buffer, message);
result.ResultStatus = ResultStatuses.Success;
Expand All @@ -68,7 +68,7 @@ public virtual StateResult Get(long lastTouched)
[HttpPut]
public virtual void Put(byte[] updatedSessionData)
{
var formatter = Serialization.SerializationFormatterFactory.GetFormatter(applicationContext);
var formatter = applicationContext.GetRequiredService<ISerializationFormatter>();
var buffer = new MemoryStream(updatedSessionData)
{
Position = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NSubstitute" Version="5.1.0" />
</ItemGroup>

<ItemGroup>
Expand Down
282 changes: 121 additions & 161 deletions Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs
Original file line number Diff line number Diff line change
@@ -1,196 +1,156 @@
//-----------------------------------------------------------------------
// <copyright file="SessionManagerTests.cs" company="Marimer LLC">
// Copyright (c) Marimer LLC. All rights reserved.
// Website: https://cslanet.com
// </copyright>
// <summary>no summary</summary>
//-----------------------------------------------------------------------

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Csla.Blazor.WebAssembly.State;
using Csla.State;
using Csla.Blazor.WebAssembly.Configuration;
using Csla.Core;
using Csla.Runtime;
using System.Net;
using Csla.Serialization;
using Csla.Serialization.Mobile;
using Microsoft.AspNetCore.Components.Authorization;
using NSubstitute;
using System.Security.Claims;
using Microsoft.Extensions.DependencyInjection;
using Csla.Configuration;

namespace Csla.Test.State;

namespace Csla.Test.State
[TestClass]
public class SessionManagerTests
{
[TestClass]
public class SessionManagerTests
private SessionManager _sessionManager;
private SessionMessage _sessionValue;

[TestInitialize]
public void Initialize()
{
private SessionManager _sessionManager;
private SessionMessage _sessionValue;
var services = new ServiceCollection();
services.AddCsla(o => o.AddBlazorWebAssembly());
var provider = services.BuildServiceProvider();
var _applicationContext = provider.GetRequiredService<ApplicationContext>();

[TestInitialize]
public void Initialize()
_sessionValue = new SessionMessage
{
var mockServiceProvider = Substitute.For<IServiceProvider>();

// Mock AuthenticationStateProvider
var mockAuthStateProvider = Substitute.For<AuthenticationStateProvider>();

// Mock IServiceProvider
mockServiceProvider.GetService(typeof(AuthenticationStateProvider)).Returns(mockAuthStateProvider);

_sessionValue = new SessionMessage
{
// Set properties here
// For example:
Principal = new Security.CslaClaimsPrincipal() { },
Session = []
};

// Mock ISerializationFormatter
var mockFormatter = Substitute.For<ISerializationFormatter>();
mockFormatter.Serialize(Arg.Any<Stream>(), Arg.Any<object>());
mockFormatter.Deserialize(Arg.Any<Stream>()).Returns(_sessionValue);

// Mock IServiceProvider
mockServiceProvider.GetService(typeof(MobileFormatter)).Returns(mockFormatter);

var mockActivator = Substitute.For<Server.IDataPortalActivator>();
mockActivator.CreateInstance(Arg.Is<Type>(t => t == typeof(MobileFormatter))).Returns(mockFormatter);
mockActivator.InitializeInstance(Arg.Any<object>());

// Mock IServiceProvider
mockServiceProvider.GetService(typeof(Server.IDataPortalActivator)).Returns(mockActivator);

// Mock IContextManager
var mockContextManager = Substitute.For<IContextManager>();
mockContextManager.IsValid.Returns(true);

// Mock IContextManagerLocal
var mockLocalContextManager = Substitute.For<IContextManagerLocal>();

// Mock IServiceProvider
mockServiceProvider.GetService(typeof(IRuntimeInfo)).Returns(new RuntimeInfo());

// Mock IEnumerable<IContextManager>
var mockContextManagerList = new List<IContextManager> { mockContextManager };

// Mock ApplicationContextAccessor
var mockApplicationContextAccessor = Substitute.For<ApplicationContextAccessor>(mockContextManagerList, mockLocalContextManager, mockServiceProvider);

var _applicationContext = new ApplicationContext(mockApplicationContextAccessor);
Principal = new ClaimsPrincipal() { },
Session = []
};

_sessionManager = new SessionManager(_applicationContext, GetHttpClient(_sessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true });
}
_sessionManager = new SessionManager(_applicationContext, GetHttpClient(_sessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true });
}

public class TestHttpMessageHandler(SessionMessage session, ApplicationContext _applicationContext) : HttpMessageHandler
{
protected override Task<HttpResponseMessage> 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)
public class TestHttpMessageHandler(SessionMessage session, ApplicationContext _applicationContext) : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var handlerMock = new TestHttpMessageHandler(session,_applicationContext);
// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock)
cancellationToken.ThrowIfCancellationRequested();
var response = new HttpResponseMessage()
{
BaseAddress = new Uri("http://test.com/"),
StatusCode = HttpStatusCode.OK,
Content = new StringContent("{\"ResultStatus\":0, \"SessionData\":\"" + Convert.ToBase64String(GetSession(session, _applicationContext)) + "\"}"),
};
return httpClient;
return Task.FromResult(response);
}

private static byte[] GetSession(SessionMessage session, ApplicationContext _applicationContext)
}
private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext)
{
var handlerMock = new TestHttpMessageHandler(session,_applicationContext);
// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock)
{
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;
}
BaseAddress = new Uri("http://test.com/"),
};
return httpClient;
}

[TestMethod]
public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException()
{
var timeout = TimeSpan.FromHours(1);
var session = await _sessionManager.RetrieveSession(timeout);
Assert.AreEqual(session, _sessionValue.Session);
}
private static byte[] GetSession(SessionMessage session, ApplicationContext _applicationContext)
{
return new MobileFormatter(_applicationContext).SerializeToByteArray(session);
}

[TestMethod]
public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException()
{
var timeout = TimeSpan.Zero;
await Assert.ThrowsExceptionAsync<TimeoutException>(() => _sessionManager.RetrieveSession(timeout));
}
[TestMethod]
public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException()
{
var timeout = TimeSpan.FromHours(1);
var session = await _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);
}
[TestMethod]
public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException()
{
var timeout = TimeSpan.Zero;
await Assert.ThrowsExceptionAsync<TimeoutException>(() => _sessionManager.RetrieveSession(timeout));
}

[TestMethod]
public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException()
{
var cts = new CancellationTokenSource();
cts.Cancel();
await Assert.ThrowsExceptionAsync<TaskCanceledException>(() => _sessionManager.RetrieveSession(cts.Token));
}
[TestMethod]
public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowException()
{
var cts = new CancellationTokenSource();
var session = await _sessionManager.RetrieveSession(cts.Token);
}

[TestMethod]
public async Task SendSession_WithTimeoutValue_ShouldNotThrowException()
{
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));
[TestMethod]
public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException()
{
var cts = new CancellationTokenSource();
cts.Cancel();
await Assert.ThrowsExceptionAsync<TaskCanceledException>(() => _sessionManager.RetrieveSession(cts.Token));
}

var timeout = TimeSpan.FromHours(1);
await _sessionManager.SendSession(timeout);
Assert.IsTrue(true);
}
[TestMethod]
public async Task SendSession_WithTimeoutValue_ShouldNotThrowException()
{
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));

[TestMethod]
public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException()
{
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));
var timeout = TimeSpan.FromHours(1);
await _sessionManager.SendSession(timeout);
Assert.IsTrue(true);
}

var timeout = TimeSpan.Zero;
await Assert.ThrowsExceptionAsync<TimeoutException>(() => _sessionManager.SendSession(timeout));
}
[TestMethod]
public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException()
{
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));

[TestMethod]
public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException()
{
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));
var timeout = TimeSpan.Zero;
await Assert.ThrowsExceptionAsync<TimeoutException>(() => _sessionManager.SendSession(timeout));
}

var cts = new CancellationTokenSource();
await _sessionManager.SendSession(cts.Token);
Assert.IsTrue(true);
}
[TestMethod]
public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException()
{
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));

[TestMethod]
public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException()
{
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));
var cts = new CancellationTokenSource();
await _sessionManager.SendSession(cts.Token);
Assert.IsTrue(true);
}

var cts = new CancellationTokenSource();
cts.Cancel();
await Assert.ThrowsExceptionAsync<TaskCanceledException>(() => _sessionManager.SendSession(cts.Token));
}
[TestMethod]
public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException()
{
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));

var cts = new CancellationTokenSource();
cts.Cancel();
await Assert.ThrowsExceptionAsync<TaskCanceledException>(() => _sessionManager.SendSession(cts.Token));
}

[TestMethod]
public async Task RetrieveCachedSessionSession()
{
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));

var session = _sessionManager.GetCachedSession();
Assert.IsNotNull(session);
}
[TestMethod]
public void RetrieveNullCachedSessionSession()
{
var session = _sessionManager.GetCachedSession();
Assert.IsNull(session);
}
[TestMethod]
public async Task RetrieveCachedSessionSession()
{
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));

var session = _sessionManager.GetCachedSession();
Assert.IsNotNull(session);
}
[TestMethod]
public void RetrieveNullCachedSessionSession()
{
var session = _sessionManager.GetCachedSession();
Assert.IsNull(session);
}
}
4 changes: 2 additions & 2 deletions Source/Csla.Blazor.WebAssembly/State/SessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public async Task<Session> RetrieveSession(CancellationToken ct)
var stateResult = await client.GetFromJsonAsync<StateResult>(url, ct).ConfigureAwait(false);
if (stateResult.ResultStatus == ResultStatuses.Success)
{
var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext);
var formatter = ApplicationContext.GetRequiredService<ISerializationFormatter>();
var buffer = new MemoryStream(stateResult.SessionData)
{
Position = 0
Expand Down Expand Up @@ -140,7 +140,7 @@ public async Task SendSession(CancellationToken ct)
_session.Touch();
if (_options.SyncContextWithServer)
{
var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext);
var formatter = ApplicationContext.GetRequiredService<ISerializationFormatter>();
var buffer = new MemoryStream();
formatter.Serialize(buffer, _session);
buffer.Position = 0;
Expand Down
1 change: 0 additions & 1 deletion Source/Csla.Blazor/ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using Csla.Core;
using Csla.Reflection;
using Csla.Rules;
using Csla.Core;

namespace Csla.Blazor
{
Expand Down
Loading

0 comments on commit 1f16f6b

Please sign in to comment.