Use JsonConverter for DotNetObjectRef
\n\nCommit migrated from 0389bf44d4
This commit is contained in:
parent
b4bcb1fd15
commit
2d2400e7fa
|
|
@ -17,12 +17,8 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
public sealed partial class DotNetObjectRef<TValue> : System.IDisposable where TValue : class
|
public sealed partial class DotNetObjectRef<TValue> : System.IDisposable where TValue : class
|
||||||
{
|
{
|
||||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
|
internal DotNetObjectRef() { }
|
||||||
public DotNetObjectRef() { }
|
|
||||||
[System.Text.Json.Serialization.JsonIgnoreAttribute]
|
|
||||||
public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
|
|
||||||
public long __dotNetObject { get { throw null; } set { } }
|
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
}
|
}
|
||||||
public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime
|
public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.JSInterop
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class DotNetDispatcher
|
public static class DotNetDispatcher
|
||||||
{
|
{
|
||||||
internal const string DotNetObjectRefKey = nameof(DotNetObjectRef<object>.__dotNetObject);
|
internal static readonly JsonEncodedText DotNetObjectRefKey = JsonEncodedText.Encode("__dotNetObject");
|
||||||
private static readonly Type[] EndInvokeParameterTypes = new Type[] { typeof(long), typeof(bool), typeof(JSAsyncCallResult) };
|
private static readonly Type[] EndInvokeParameterTypes = new Type[] { typeof(long), typeof(bool), typeof(JSAsyncCallResult) };
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<AssemblyKey, IReadOnlyDictionary<string, (MethodInfo, Type[])>> _cachedMethodsByAssembly
|
private static readonly ConcurrentDictionary<AssemblyKey, IReadOnlyDictionary<string, (MethodInfo, Type[])>> _cachedMethodsByAssembly
|
||||||
|
|
@ -238,7 +238,7 @@ namespace Microsoft.JSInterop
|
||||||
// an incorrect use if there's a object that looks like { '__dotNetObject': <some number> },
|
// an incorrect use if there's a object that looks like { '__dotNetObject': <some number> },
|
||||||
// but we aren't assigning to DotNetObjectRef{T}.
|
// but we aren't assigning to DotNetObjectRef{T}.
|
||||||
return item.ValueKind == JsonValueKind.Object &&
|
return item.ValueKind == JsonValueKind.Object &&
|
||||||
item.TryGetProperty(DotNetObjectRefKey, out _) &&
|
item.TryGetProperty(DotNetObjectRefKey.EncodedUtf8Bytes, out _) &&
|
||||||
!typeof(IDotNetObjectRef).IsAssignableFrom(parameterType);
|
!typeof(IDotNetObjectRef).IsAssignableFrom(parameterType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ namespace Microsoft.JSInterop
|
||||||
/// <returns>An instance of <see cref="DotNetObjectRef{TValue}" />.</returns>
|
/// <returns>An instance of <see cref="DotNetObjectRef{TValue}" />.</returns>
|
||||||
public static DotNetObjectRef<TValue> Create<TValue>(TValue value) where TValue : class
|
public static DotNetObjectRef<TValue> Create<TValue>(TValue value) where TValue : class
|
||||||
{
|
{
|
||||||
return new DotNetObjectRef<TValue>(value);
|
var objectId = DotNetObjectRefManager.Current.TrackObject(value);
|
||||||
|
return new DotNetObjectRef<TValue>(objectId, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop
|
||||||
|
{
|
||||||
|
internal sealed class DotNetObjectReferenceJsonConverterFactory : JsonConverterFactory
|
||||||
|
{
|
||||||
|
public override bool CanConvert(Type typeToConvert)
|
||||||
|
{
|
||||||
|
Debug.Assert(
|
||||||
|
typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DotNetObjectRef<>),
|
||||||
|
"We expect this to only be called for DotNetObjectRef<T> instances.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override JsonConverter CreateConverter(Type typeToConvert)
|
||||||
|
{
|
||||||
|
// System.Text.Json handles caching the converters per type on our behalf. No caching is required here.
|
||||||
|
var instanceType = typeToConvert.GetGenericArguments()[0];
|
||||||
|
var converterType = typeof(DotNetObjectReferenceJsonConverter<>).MakeGenericType(instanceType);
|
||||||
|
|
||||||
|
return (JsonConverter)Activator.CreateInstance(converterType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.JSInterop
|
||||||
internal class DotNetObjectRefManager
|
internal class DotNetObjectRefManager
|
||||||
{
|
{
|
||||||
private long _nextId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1
|
private long _nextId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1
|
||||||
private readonly ConcurrentDictionary<long, IDotNetObjectRef> _trackedRefsById = new ConcurrentDictionary<long, IDotNetObjectRef>();
|
private readonly ConcurrentDictionary<long, object> _trackedRefsById = new ConcurrentDictionary<long, object>();
|
||||||
|
|
||||||
public static DotNetObjectRefManager Current
|
public static DotNetObjectRefManager Current
|
||||||
{
|
{
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.JSInterop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long TrackObject(IDotNetObjectRef dotNetObjectRef)
|
public long TrackObject(object dotNetObjectRef)
|
||||||
{
|
{
|
||||||
var dotNetObjectId = Interlocked.Increment(ref _nextId);
|
var dotNetObjectId = Interlocked.Increment(ref _nextId);
|
||||||
_trackedRefsById[dotNetObjectId] = dotNetObjectRef;
|
_trackedRefsById[dotNetObjectId] = dotNetObjectRef;
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.JSInterop
|
||||||
public object FindDotNetObject(long dotNetObjectId)
|
public object FindDotNetObject(long dotNetObjectId)
|
||||||
{
|
{
|
||||||
return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef)
|
return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef)
|
||||||
? dotNetObjectRef.Value
|
? dotNetObjectRef
|
||||||
: throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the DotNetObjectRef instance was already disposed.", nameof(dotNetObjectId));
|
: throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the DotNetObjectRef instance was already disposed.", nameof(dotNetObjectId));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop
|
||||||
|
|
@ -14,54 +13,26 @@ namespace Microsoft.JSInterop
|
||||||
/// To avoid leaking memory, the reference must later be disposed by JS code or by .NET code.
|
/// To avoid leaking memory, the reference must later be disposed by JS code or by .NET code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TValue">The type of the value to wrap.</typeparam>
|
/// <typeparam name="TValue">The type of the value to wrap.</typeparam>
|
||||||
|
[JsonConverter(typeof(DotNetObjectReferenceJsonConverterFactory))]
|
||||||
public sealed class DotNetObjectRef<TValue> : IDotNetObjectRef, IDisposable where TValue : class
|
public sealed class DotNetObjectRef<TValue> : IDotNetObjectRef, IDisposable where TValue : class
|
||||||
{
|
{
|
||||||
private long? _trackingId;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API is for meant for JSON deserialization and should not be used by user code.
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public DotNetObjectRef()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of <see cref="DotNetObjectRef{TValue}" />.
|
/// Initializes a new instance of <see cref="DotNetObjectRef{TValue}" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="objectId">The object Id.</param>
|
||||||
/// <param name="value">The value to pass by reference.</param>
|
/// <param name="value">The value to pass by reference.</param>
|
||||||
internal DotNetObjectRef(TValue value)
|
internal DotNetObjectRef(long objectId, TValue value)
|
||||||
{
|
{
|
||||||
|
ObjectId = objectId;
|
||||||
Value = value;
|
Value = value;
|
||||||
_trackingId = DotNetObjectRefManager.Current.TrackObject(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the object instance represented by this wrapper.
|
/// Gets the object instance represented by this wrapper.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
public TValue Value { get; }
|
||||||
public TValue Value { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
internal long ObjectId { get; }
|
||||||
/// This API is for meant for JSON serialization and should not be used by user code.
|
|
||||||
/// </summary>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public long __dotNetObject
|
|
||||||
{
|
|
||||||
get => _trackingId.Value;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_trackingId != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"{nameof(DotNetObjectRef<TValue>)} cannot be reinitialized.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_trackingId = value;
|
|
||||||
Value = (TValue)DotNetObjectRefManager.Current.FindDotNetObject(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object IDotNetObjectRef.Value => Value;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops tracking this object reference, allowing it to be garbage collected
|
/// Stops tracking this object reference, allowing it to be garbage collected
|
||||||
|
|
@ -70,7 +41,7 @@ namespace Microsoft.JSInterop
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
DotNetObjectRefManager.Current.ReleaseDotNetObject(_trackingId.Value);
|
DotNetObjectRefManager.Current.ReleaseDotNetObject(ObjectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop
|
||||||
|
{
|
||||||
|
internal sealed class DotNetObjectReferenceJsonConverter<TValue> : JsonConverter<DotNetObjectRef<TValue>> where TValue : class
|
||||||
|
{
|
||||||
|
private static JsonEncodedText DotNetObjectRefKey => DotNetDispatcher.DotNetObjectRefKey;
|
||||||
|
|
||||||
|
public override DotNetObjectRef<TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (!reader.Read())
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Invalid DotNetObjectRef JSON.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.TokenType != JsonTokenType.PropertyName || !reader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes))
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Invalid DotNetObjectRef JSON.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reader.Read())
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Invalid DotNetObjectRef JSON.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var dotNetObjectId = reader.GetInt64();
|
||||||
|
|
||||||
|
if (!reader.Read())
|
||||||
|
{
|
||||||
|
// We need to read all the data that was given to us.
|
||||||
|
throw new InvalidDataException("Invalid DotNetObjectRef JSON.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = (TValue)DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId);
|
||||||
|
return new DotNetObjectRef<TValue>(dotNetObjectId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, DotNetObjectRef<TValue> value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteStartObject();
|
||||||
|
writer.WriteNumber(DotNetObjectRefKey, value.ObjectId);
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,5 @@ namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
internal interface IDotNetObjectRef : IDisposable
|
internal interface IDotNetObjectRef : IDisposable
|
||||||
{
|
{
|
||||||
public object Value { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop
|
||||||
|
{
|
||||||
|
internal class TestJSRuntime : JSRuntimeBase
|
||||||
|
{
|
||||||
|
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task WithJSRuntime(Action<JSRuntimeBase> testCode)
|
||||||
|
{
|
||||||
|
// Since the tests rely on the asynclocal JSRuntime.Current, ensure we
|
||||||
|
// are on a distinct async context with a non-null JSRuntime.Current
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var runtime = new TestJSRuntime();
|
||||||
|
JSRuntime.SetCurrentJSRuntime(runtime);
|
||||||
|
testCode(runtime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -142,7 +142,7 @@ namespace Microsoft.JSInterop.Tests
|
||||||
Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.StringVal), out _));
|
Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.StringVal), out _));
|
||||||
Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.IntVal), out _));
|
Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.IntVal), out _));
|
||||||
|
|
||||||
Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey, out var property));
|
Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey.EncodedUtf8Bytes, out var property));
|
||||||
var resultDto2 = Assert.IsType<TestDTO>(DotNetObjectRefManager.Current.FindDotNetObject(property.GetInt64()));
|
var resultDto2 = Assert.IsType<TestDTO>(DotNetObjectRefManager.Current.FindDotNetObject(property.GetInt64()));
|
||||||
Assert.Equal("MY STRING", resultDto2.StringVal);
|
Assert.Equal("MY STRING", resultDto2.StringVal);
|
||||||
Assert.Equal(1299, resultDto2.IntVal);
|
Assert.Equal(1299, resultDto2.IntVal);
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using static Microsoft.JSInterop.TestJSRuntime;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop.Tests
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
public class DotNetObjectRefTest
|
public class DotNetObjectRefTest
|
||||||
{
|
{
|
||||||
|
|
@ -21,21 +22,14 @@ namespace Microsoft.JSInterop.Tests
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var objRef = DotNetObjectRef.Create(new object());
|
var objRef = DotNetObjectRef.Create(new object());
|
||||||
var trackingId = objRef.__dotNetObject;
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
objRef.Dispose();
|
objRef.Dispose();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var ex = Assert.Throws<ArgumentException>(() => jsRuntime.ObjectRefManager.FindDotNetObject(trackingId));
|
var ex = Assert.Throws<ArgumentException>(() => jsRuntime.ObjectRefManager.FindDotNetObject(objRef.ObjectId));
|
||||||
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
|
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
|
||||||
});
|
});
|
||||||
|
|
||||||
class TestJSRuntime : JSRuntimeBase
|
|
||||||
{
|
|
||||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId)
|
protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
using static Microsoft.JSInterop.TestJSRuntime;
|
||||||
|
|
||||||
|
namespace Microsoft.JSInterop.Tests
|
||||||
|
{
|
||||||
|
public class DotNetObjectReferenceJsonConverterTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public Task Read_ReadsJson() => WithJSRuntime(_ =>
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// Throw-away value
|
||||||
|
DotNetObjectRef.Create(new TestModel());
|
||||||
|
var input = new TestModel();
|
||||||
|
var dotNetObjectRef = DotNetObjectRef.Create(input);
|
||||||
|
var objectId = dotNetObjectRef.ObjectId;
|
||||||
|
|
||||||
|
var json = $"{{\"__dotNetObject\":{objectId}}}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var deserialized = JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(input, deserialized.Value);
|
||||||
|
Assert.Equal(objectId, deserialized.ObjectId);
|
||||||
|
});
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task Read_ReadsJson_WithFormatting() => WithJSRuntime(_ =>
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// Throw-away value
|
||||||
|
DotNetObjectRef.Create(new TestModel());
|
||||||
|
var input = new TestModel();
|
||||||
|
var dotNetObjectRef = DotNetObjectRef.Create(input);
|
||||||
|
var objectId = dotNetObjectRef.ObjectId;
|
||||||
|
|
||||||
|
var json =
|
||||||
|
@$"{{
|
||||||
|
""__dotNetObject"": {objectId}
|
||||||
|
}}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var deserialized = JsonSerializer.Deserialize<DotNetObjectRef<TestModel>>(json);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(input, deserialized.Value);
|
||||||
|
Assert.Equal(objectId, deserialized.ObjectId);
|
||||||
|
});
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task WriteJsonTwice_KeepsObjectId() => WithJSRuntime(_ =>
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// Throw-away value
|
||||||
|
DotNetObjectRef.Create(new TestModel());
|
||||||
|
var dotNetObjectRef = DotNetObjectRef.Create(new TestModel());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var json1 = JsonSerializer.Serialize(dotNetObjectRef);
|
||||||
|
var json2 = JsonSerializer.Serialize(dotNetObjectRef);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal($"{{\"__dotNetObject\":{dotNetObjectRef.ObjectId}}}", json1);
|
||||||
|
Assert.Equal(json1, json2);
|
||||||
|
});
|
||||||
|
|
||||||
|
private class TestModel
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue