Skip to content

Commit d28dc8d

Browse files
Implement IMobileObjectMetastate on ReadOnlyListBase (#4766)
* Initial plan * Add IMobileObjectMetastate implementation to ReadOnlyListBase and create tests Co-authored-by: rockfordlhotka <2333134+rockfordlhotka@users.noreply.github.com> * Fix JsonElement handling in MobileObjectMetastateHelper and update tests Co-authored-by: rockfordlhotka <2333134+rockfordlhotka@users.noreply.github.com> * Final implementation complete Co-authored-by: rockfordlhotka <2333134+rockfordlhotka@users.noreply.github.com> * Remove accidentally added nuget.exe --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rockfordlhotka <2333134+rockfordlhotka@users.noreply.github.com>
1 parent e0573f1 commit d28dc8d

File tree

3 files changed

+189
-2
lines changed

3 files changed

+189
-2
lines changed

Source/Csla/ReadOnlyListBase.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Diagnostics.CodeAnalysis;
1111
using Csla.Core;
1212
using Csla.Properties;
13+
using Csla.Serialization.Mobile;
1314
using Csla.Server;
1415

1516
namespace Csla
@@ -30,7 +31,8 @@ public abstract class ReadOnlyListBase<T, C> :
3031
#endif
3132
IDataPortalTarget,
3233
IReadOnlyListBase<C>,
33-
IUseApplicationContext
34+
IUseApplicationContext,
35+
IMobileObjectMetastate
3436
where T : ReadOnlyListBase<T, C>
3537
{
3638
/// <summary>
@@ -252,5 +254,21 @@ void IDataPortalTarget.Child_OnDataPortalException(DataPortalEventArgs e, Except
252254
}
253255

254256
#endregion
257+
258+
#region IMobileObjectMetastate Members
259+
260+
/// <inheritdoc />
261+
byte[] IMobileObjectMetastate.GetMetastate()
262+
{
263+
return MobileObjectMetastateHelper.SerializeMetastate(this);
264+
}
265+
266+
/// <inheritdoc />
267+
void IMobileObjectMetastate.SetMetastate(byte[] metastate)
268+
{
269+
MobileObjectMetastateHelper.DeserializeMetastate(this, metastate);
270+
}
271+
272+
#endregion
255273
}
256274
}

Source/Csla/Serialization/Mobile/MobileObjectMetastateHelper.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,43 @@ private static void PopulateSerializationInfo(SerializationInfo info,
9393
{
9494
foreach (var kvp in values)
9595
{
96-
info.AddValue(kvp.Key, kvp.Value.Value, kvp.Value.IsDirty, kvp.Value.EnumTypeName);
96+
var value = kvp.Value.Value;
97+
98+
// Convert JsonElement to appropriate type if needed
99+
if (value is JsonElement jsonElement)
100+
{
101+
value = ConvertJsonElement(jsonElement);
102+
}
103+
104+
info.AddValue(kvp.Key, value, kvp.Value.IsDirty, kvp.Value.EnumTypeName);
105+
}
106+
}
107+
108+
private static object? ConvertJsonElement(JsonElement element)
109+
{
110+
switch (element.ValueKind)
111+
{
112+
case JsonValueKind.String:
113+
return element.GetString();
114+
case JsonValueKind.Number:
115+
if (element.TryGetInt32(out int intValue))
116+
return intValue;
117+
if (element.TryGetInt64(out long longValue))
118+
return longValue;
119+
if (element.TryGetDouble(out double doubleValue))
120+
return doubleValue;
121+
return element.GetDecimal();
122+
case JsonValueKind.True:
123+
return true;
124+
case JsonValueKind.False:
125+
return false;
126+
case JsonValueKind.Null:
127+
case JsonValueKind.Undefined:
128+
return null;
129+
default:
130+
// For complex types, return the element as-is and let the normal
131+
// conversion logic handle it
132+
return element;
97133
}
98134
}
99135

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="ReadOnlyListBaseMetastateTests.cs" company="Marimer LLC">
3+
// Copyright (c) Marimer LLC. All rights reserved.
4+
// Website: https://cslanet.com
5+
// </copyright>
6+
// <summary>Tests for IMobileObjectMetastate interface implementation on ReadOnlyListBase.</summary>
7+
//-----------------------------------------------------------------------
8+
9+
using Microsoft.VisualStudio.TestTools.UnitTesting;
10+
using Csla.Serialization.Mobile;
11+
using Csla.TestHelpers;
12+
13+
namespace Csla.Test.Serialization
14+
{
15+
[TestClass]
16+
public class ReadOnlyListBaseMetastateTests
17+
{
18+
private static TestDIContext _testDIContext;
19+
20+
[ClassInitialize]
21+
public static void ClassInitialize(TestContext context)
22+
{
23+
_testDIContext = TestDIContextFactory.CreateDefaultContext();
24+
}
25+
26+
[TestInitialize]
27+
public void Initialize()
28+
{
29+
TestResults.Reinitialise();
30+
}
31+
32+
[TestMethod]
33+
public void ReadOnlyListBase_GetSetMetastate_IsReadOnly_RoundTrip()
34+
{
35+
// Arrange
36+
var dataPortal = _testDIContext.CreateDataPortal<TestReadOnlyList>();
37+
var original = dataPortal.Fetch();
38+
39+
// The list should be readonly by default
40+
Assert.IsTrue(original.IsReadOnly);
41+
42+
// Act - Get the metastate
43+
var metastate = ((IMobileObjectMetastate)original).GetMetastate();
44+
45+
// Create a new instance and restore the metastate
46+
var restored = dataPortal.Fetch();
47+
48+
// Verify before setting metastate
49+
Assert.IsTrue(restored.IsReadOnly);
50+
51+
// Set the metastate
52+
((IMobileObjectMetastate)restored).SetMetastate(metastate);
53+
54+
// Assert - The IsReadOnly property should be preserved
55+
Assert.AreEqual(original.IsReadOnly, restored.IsReadOnly);
56+
}
57+
58+
[TestMethod]
59+
public void ReadOnlyListBase_GetSetMetastate_IsReadOnly_False()
60+
{
61+
// Arrange
62+
var dataPortal = _testDIContext.CreateDataPortal<TestReadOnlyList>();
63+
var original = dataPortal.Fetch();
64+
65+
// Unlock the list to test IsReadOnly = false
66+
original.SetIsReadOnlyForTest(false);
67+
Assert.IsFalse(original.IsReadOnly);
68+
69+
// Act - Get the metastate
70+
var metastate = ((IMobileObjectMetastate)original).GetMetastate();
71+
72+
// Create a new instance (which will be readonly by default)
73+
var restored = dataPortal.Fetch();
74+
Assert.IsTrue(restored.IsReadOnly);
75+
76+
// Set the metastate
77+
((IMobileObjectMetastate)restored).SetMetastate(metastate);
78+
79+
// Assert - The IsReadOnly property should be false after restore
80+
Assert.AreEqual(original.IsReadOnly, restored.IsReadOnly);
81+
Assert.IsFalse(restored.IsReadOnly);
82+
}
83+
84+
[TestMethod]
85+
[ExpectedException(typeof(ArgumentNullException))]
86+
public void ReadOnlyListBase_SetMetastate_ThrowsOnNullMetastate()
87+
{
88+
// Arrange
89+
var dataPortal = _testDIContext.CreateDataPortal<TestReadOnlyList>();
90+
var list = dataPortal.Fetch();
91+
92+
// Act
93+
((IMobileObjectMetastate)list).SetMetastate(null);
94+
}
95+
96+
[TestMethod]
97+
[ExpectedException(typeof(ArgumentException))]
98+
public void ReadOnlyListBase_SetMetastate_ThrowsOnEmptyMetastate()
99+
{
100+
// Arrange
101+
var dataPortal = _testDIContext.CreateDataPortal<TestReadOnlyList>();
102+
var list = dataPortal.Fetch();
103+
104+
// Act
105+
((IMobileObjectMetastate)list).SetMetastate(new byte[0]);
106+
}
107+
108+
#region Test Helper Classes
109+
110+
[Serializable]
111+
public class TestReadOnlyList : ReadOnlyListBase<TestReadOnlyList, string>
112+
{
113+
[Fetch]
114+
private void DataPortal_Fetch()
115+
{
116+
// Populate with some simple test data
117+
IsReadOnly = false;
118+
Add("Item1");
119+
Add("Item2");
120+
Add("Item3");
121+
IsReadOnly = true;
122+
}
123+
124+
// Expose a method to set IsReadOnly for testing
125+
public void SetIsReadOnlyForTest(bool value)
126+
{
127+
IsReadOnly = value;
128+
}
129+
}
130+
131+
#endregion
132+
}
133+
}

0 commit comments

Comments
 (0)