From 6f05f83a97e3b41dde7ba1c243b4857eda35ec3a Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 16 Jul 2019 09:36:15 -0700 Subject: [PATCH 1/3] Use a JsonConverter with ElementRef --- ...ft.AspNetCore.Components.netstandard2.0.cs | 2 - .../Components/src/ElementReference.cs | 50 ++++++++++--- .../Components/test/ElementReferenceTest.cs | 70 +++++++++++++++++++ 3 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 src/Components/Components/test/ElementReferenceTest.cs 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 c887990cdb..2ceaab1767 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..adb1ebbc43 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,45 @@ 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.ValueTextEquals(IdProperty.EncodedUtf8Bytes)) + { + reader.Read(); + id = reader.GetString(); + } + } + + 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..deb048716d --- /dev/null +++ b/src/Components/Components/test/ElementReferenceTest.cs @@ -0,0 +1,70 @@ +// 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.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_IfIdIsNotSpecified() + { + // Arrange + var json = "{\"id\":\"some-value\"}"; + + // Act + var ex = Assert.Throws(() => JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options)); + + // Assert + Assert.Equal("__internalId is required.", ex.Message); + } + } +} From 74451ed348149544e2e02ae5f0a049a67a6c2ed7 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 17 Jul 2019 11:25:06 -0700 Subject: [PATCH 2/3] Skip test --- .../Blazor/Build/test/RuntimeDependenciesResolverTest.cs | 2 +- src/Components/Components/test/ElementReferenceTest.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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/test/ElementReferenceTest.cs b/src/Components/Components/test/ElementReferenceTest.cs index deb048716d..496a3bbe33 100644 --- a/src/Components/Components/test/ElementReferenceTest.cs +++ b/src/Components/Components/test/ElementReferenceTest.cs @@ -1,7 +1,6 @@ // 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.Text.Json; using Xunit; From 919bd7db063b1d66eb38d84030ff19c5aa499e0a Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 17 Jul 2019 15:23:24 -0700 Subject: [PATCH 3/3] Disallow random properties --- .../Components/src/ElementReference.cs | 17 ++++++++++++++--- .../Components/test/ElementReferenceTest.cs | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Components/Components/src/ElementReference.cs b/src/Components/Components/src/ElementReference.cs index adb1ebbc43..92affa1249 100644 --- a/src/Components/Components/src/ElementReference.cs +++ b/src/Components/Components/src/ElementReference.cs @@ -64,10 +64,21 @@ namespace Microsoft.AspNetCore.Components string id = null; while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { - if (reader.ValueTextEquals(IdProperty.EncodedUtf8Bytes)) + if (reader.TokenType == JsonTokenType.PropertyName) { - reader.Read(); - id = reader.GetString(); + 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}."); } } diff --git a/src/Components/Components/test/ElementReferenceTest.cs b/src/Components/Components/test/ElementReferenceTest.cs index 496a3bbe33..39c947aaf0 100644 --- a/src/Components/Components/test/ElementReferenceTest.cs +++ b/src/Components/Components/test/ElementReferenceTest.cs @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Components } [Fact] - public void Deserializing_Throws_IfIdIsNotSpecified() + public void Deserializing_Throws_IfUnknownPropertyAppears() { // Arrange var json = "{\"id\":\"some-value\"}"; @@ -62,6 +62,19 @@ namespace Microsoft.AspNetCore.Components // 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); }