diff --git a/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs b/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs index bf96e57a93..dd3456d99d 100644 --- a/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs +++ b/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test { public class RuntimeDependenciesResolverTest { - [ConditionalFact] + [ConditionalFact(Skip = " https://github.com/aspnet/AspNetCore/issues/12059")] [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10426")] public void FindsReferenceAssemblyGraph_ForStandaloneApp() { diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs index 93ae0a2b02..a5404c911e 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs @@ -154,8 +154,6 @@ namespace Microsoft.AspNetCore.Components public readonly partial struct ElementReference { private readonly object _dummy; - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public string __internalId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct EventCallback diff --git a/src/Components/Components/src/ElementReference.cs b/src/Components/Components/src/ElementReference.cs index be85bffab0..92affa1249 100644 --- a/src/Components/Components/src/ElementReference.cs +++ b/src/Components/Components/src/ElementReference.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.ComponentModel; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading; namespace Microsoft.AspNetCore.Components @@ -10,25 +12,23 @@ namespace Microsoft.AspNetCore.Components /// /// Represents a reference to a rendered element. /// + [JsonConverter(typeof(ElementReferenceConverter))] public readonly struct ElementReference { private static long _nextIdForWebAssemblyOnly = 1; /// - /// Gets a unique identifier for . + /// Gets a unique identifier for . /// /// /// The Id is unique at least within the scope of a given user/circuit. /// This property is public to support Json serialization and should not be used by user code. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public string __internalId { get; } - - internal string Id => __internalId; + internal string Id { get; } private ElementReference(string id) { - __internalId = id; + Id = id; } internal static ElementReference CreateWithUniqueId() @@ -45,13 +45,56 @@ namespace Microsoft.AspNetCore.Components // fields for ElementRefCaptureId, of which only one would be in use depending // on the platform. var id = Interlocked.Increment(ref _nextIdForWebAssemblyOnly); - return id.ToString(); + return id.ToString(CultureInfo.InvariantCulture); } else { // For remote rendering, it's important not to disclose any cross-user state, // such as the number of IDs that have been assigned. - return Guid.NewGuid().ToString("D"); + return Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture); + } + } + + private sealed class ElementReferenceConverter : JsonConverter + { + private static readonly JsonEncodedText IdProperty = JsonEncodedText.Encode("__internalId"); + + public override ElementReference Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string id = null; + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType == JsonTokenType.PropertyName) + { + if (reader.ValueTextEquals(IdProperty.EncodedUtf8Bytes)) + { + reader.Read(); + id = reader.GetString(); + } + else + { + throw new JsonException($"Unexpected JSON property '{reader.GetString()}'."); + } + } + else + { + throw new JsonException($"Unexcepted JSON Token {reader.TokenType}."); + } + } + + if (id is null) + { + throw new JsonException("__internalId is required."); + } + + return new ElementReference(id); + } + + public override void Write(Utf8JsonWriter writer, ElementReference value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteString(IdProperty, value.Id); + writer.WriteEndObject(); } } } diff --git a/src/Components/Components/test/ElementReferenceTest.cs b/src/Components/Components/test/ElementReferenceTest.cs new file mode 100644 index 0000000000..39c947aaf0 --- /dev/null +++ b/src/Components/Components/test/ElementReferenceTest.cs @@ -0,0 +1,82 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Components +{ + public class ElementReferenceTest + { + [Fact] + public void Serializing_Works() + { + // Arrange + var elementReference = ElementReference.CreateWithUniqueId(); + var expected = $"{{\"__internalId\":\"{elementReference.Id}\"}}"; + + // Act + var json = JsonSerializer.Serialize(elementReference, JsonSerializerOptionsProvider.Options); + + // Assert + Assert.Equal(expected, json); + } + + [Fact] + public void Deserializing_Works() + { + // Arrange + var id = ElementReference.CreateWithUniqueId().Id; + var json = $"{{\"__internalId\":\"{id}\"}}"; + + // Act + var elementReference = JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options); + + // Assert + Assert.Equal(id, elementReference.Id); + } + + [Fact] + public void Deserializing_WithFormatting_Works() + { + // Arrange + var id = ElementReference.CreateWithUniqueId().Id; + var json = +@$"{{ + ""__internalId"": ""{id}"" +}}"; + + // Act + var elementReference = JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options); + + // Assert + Assert.Equal(id, elementReference.Id); + } + + [Fact] + public void Deserializing_Throws_IfUnknownPropertyAppears() + { + // Arrange + var json = "{\"id\":\"some-value\"}"; + + // Act + var ex = Assert.Throws(() => JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options)); + + // Assert + Assert.Equal("Unexpected JSON property 'id'.", ex.Message); + } + + [Fact] + public void Deserializing_Throws_IfIdIsNotSpecified() + { + // Arrange + var json = "{}"; + + // Act + var ex = Assert.Throws(() => JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options)); + + // Assert + Assert.Equal("__internalId is required.", ex.Message); + } + } +}