From 6c5274a09fa80992b90844e0d32e2fe4d12265f2 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 16 May 2019 17:39:03 -0700 Subject: [PATCH 1/2] Support a few more complex types with DefaultTempDataSerializer Fixes https://github.com/aspnet/AspNetCore/issues/9540 --- .../Identity.DefaultUI.WebSite/StartupBase.cs | 3 +- .../test/BsonTempDataSerializerTest.cs | 28 +++++ .../DefaultTempDataSerializer.cs | 87 +++++++++++++- .../DefaultTempDataSerializerTest.cs | 21 ++++ .../TempDataSerializerTestBase.cs | 113 ++++++++++++++++++ src/Mvc/MvcNoDeps.slnf | 73 +++++++++++ 6 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 src/Mvc/MvcNoDeps.slnf diff --git a/src/Identity/testassets/Identity.DefaultUI.WebSite/StartupBase.cs b/src/Identity/testassets/Identity.DefaultUI.WebSite/StartupBase.cs index 14ae5b2db4..f3915ed44e 100644 --- a/src/Identity/testassets/Identity.DefaultUI.WebSite/StartupBase.cs +++ b/src/Identity/testassets/Identity.DefaultUI.WebSite/StartupBase.cs @@ -50,8 +50,7 @@ namespace Identity.DefaultUI.WebSite .AddRoles() .AddEntityFrameworkStores(); - services.AddMvc() - .AddNewtonsoftJson(); + services.AddMvc(); services.AddSingleton(); } diff --git a/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs b/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs index a2438b1b8b..bae4f65fd7 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs @@ -235,6 +235,34 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson Assert.Equal(value.ToString(), roundTripValue); } + [Fact] + public void RoundTripTest_ListOfDateTime() + { + // Documents the behavior of round-tripping a Guid value as a string + // Arrange + var key = "test-key"; + var dateTime = new DateTime(2007, 1, 1); + var testProvider = GetTempDataSerializer(); + var value = new List + { + dateTime, + dateTime.AddDays(3), + }; + + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType(values[key]); + Assert.Equal(value, roundTripValue); + } + private class TestItem { public int DummyInt { get; set; } diff --git a/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs index 27b30c9dc6..a67670e595 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Infrastructure/DefaultTempDataSerializer.cs @@ -63,6 +63,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure deserializedValue = null; break; + case JsonValueType.Array: + deserializedValue = DeserializeArray(item.Value); + break; + + case JsonValueType.Object: + deserializedValue = DeserializeDictionaryEntry(item.Value); + break; + default: throw new InvalidOperationException(Resources.FormatTempData_CannotDeserializeType(item.Value.Type)); } @@ -73,6 +81,53 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure return deserialized; } + private static object DeserializeArray(in JsonElement arrayElement) + { + if (arrayElement.GetArrayLength() == 0) + { + // We have to infer the type of the array by inspecting it's elements. + // If there's nothing to inspect, return a null value since we do not know + // what type the user code is expecting. + return null; + } + + if (arrayElement[0].Type == JsonValueType.String) + { + var array = new List(); + + foreach (var item in arrayElement.EnumerateArray()) + { + array.Add(item.GetString()); + } + + return array.ToArray(); + } + else if (arrayElement[0].Type == JsonValueType.Number) + { + var array = new List(); + + foreach (var item in arrayElement.EnumerateArray()) + { + array.Add(item.GetInt32()); + } + + return array.ToArray(); + } + + throw new InvalidOperationException(Resources.FormatTempData_CannotDeserializeType(arrayElement.Type)); + } + + private static object DeserializeDictionaryEntry(in JsonElement objectElement) + { + var dictionary = new Dictionary(StringComparer.Ordinal); + foreach (var item in objectElement.EnumerateObject()) + { + dictionary[item.Name] = item.Value.GetString(); + } + + return dictionary; + } + public override byte[] Serialize(IDictionary values) { if (values == null || values.Count == 0) @@ -126,6 +181,33 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure case Guid guid: writer.WriteString(key, guid); break; + + case ICollection intCollection: + writer.WriteStartArray(key); + foreach (var element in intCollection) + { + writer.WriteNumberValue(element); + } + writer.WriteEndArray(); + break; + + case ICollection stringCollection: + writer.WriteStartArray(key); + foreach (var element in stringCollection) + { + writer.WriteStringValue(element); + } + writer.WriteEndArray(); + break; + + case IDictionary dictionary: + writer.WriteStartObject(key); + foreach (var element in dictionary) + { + writer.WriteString(element.Key, element.Value); + } + writer.WriteEndObject(); + break; } } writer.WriteEndObject(); @@ -150,7 +232,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure type == typeof(string) || type == typeof(bool) || type == typeof(DateTime) || - type == typeof(Guid); + type == typeof(Guid) || + typeof(ICollection).IsAssignableFrom(type) || + typeof(ICollection).IsAssignableFrom(type) || + typeof(IDictionary).IsAssignableFrom(type); } } } diff --git a/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/DefaultTempDataSerializerTest.cs b/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/DefaultTempDataSerializerTest.cs index 982e9fc9f1..22f2185fd1 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/DefaultTempDataSerializerTest.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/DefaultTempDataSerializerTest.cs @@ -34,5 +34,26 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure var roundTripValue = Assert.IsType(values[key]); Assert.Equal(value.ToString("r"), roundTripValue); } + + [Fact] + public override void RoundTripTest_DictionaryOfInt() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new Dictionary + { + { "Key1", 7 }, + { "Key2", 24 }, + }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var ex = Assert.Throws(() => testProvider.Serialize(input)); + Assert.Equal($"The '{testProvider.GetType()}' cannot serialize an object of type '{value.GetType()}'.", ex.Message); + } } } diff --git a/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/TempDataSerializerTestBase.cs b/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/TempDataSerializerTestBase.cs index 38a540f8e7..93fb4df0e2 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/TempDataSerializerTestBase.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/Infrastructure/TempDataSerializerTestBase.cs @@ -239,6 +239,119 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure Assert.Equal(value, roundTripValue); } + [Fact] + public void RoundTripTest_CollectionOfInts() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new[] { 1, 2, 4, 3 }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType(values[key]); + Assert.Equal(value, roundTripValue); + } + + [Fact] + public void RoundTripTest_ArrayOfStringss() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new[] { "Hello", "world" }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType(values[key]); + Assert.Equal(value, roundTripValue); + } + + [Fact] + public void RoundTripTest_ListOfStringss() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new List { "Hello", "world" }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType(values[key]); + Assert.Equal(value, roundTripValue); + } + + [Fact] + public void RoundTripTest_DictionaryOfString() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new Dictionary + { + { "Key1", "Value1" }, + { "Key2", "Value2" }, + }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType>(values[key]); + Assert.Equal(value, roundTripValue); + } + + [Fact] + public virtual void RoundTripTest_DictionaryOfInt() + { + // Arrange + var key = "test-key"; + var testProvider = GetTempDataSerializer(); + var value = new Dictionary + { + { "Key1", 7 }, + { "Key2", 24 }, + }; + var input = new Dictionary + { + { key, value } + }; + + // Act + var bytes = testProvider.Serialize(input); + var values = testProvider.Deserialize(bytes); + + // Assert + var roundTripValue = Assert.IsType>(values[key]); + Assert.Equal(value, roundTripValue); + } + protected abstract TempDataSerializer GetTempDataSerializer(); } } diff --git a/src/Mvc/MvcNoDeps.slnf b/src/Mvc/MvcNoDeps.slnf new file mode 100644 index 0000000000..f67bdb6d53 --- /dev/null +++ b/src/Mvc/MvcNoDeps.slnf @@ -0,0 +1,73 @@ +{ + "solution": { + "path": "Mvc.sln", + "projects": [ + "test\\WebSites\\BasicWebSite\\BasicWebSite.csproj", + "test\\WebSites\\RoutingWebSite\\Mvc.RoutingWebSite.csproj", + "test\\WebSites\\RazorWebSite\\RazorWebSite.csproj", + "test\\WebSites\\FormatterWebSite\\FormatterWebSite.csproj", + "test\\WebSites\\ApiExplorerWebSite\\ApiExplorerWebSite.csproj", + "test\\WebSites\\VersioningWebSite\\VersioningWebSite.csproj", + "test\\WebSites\\TagHelpersWebSite\\TagHelpersWebSite.csproj", + "test\\WebSites\\FilesWebSite\\FilesWebSite.csproj", + "test\\WebSites\\ApplicationModelWebSite\\ApplicationModelWebSite.csproj", + "test\\WebSites\\HtmlGenerationWebSite\\HtmlGenerationWebSite.csproj", + "test\\WebSites\\ErrorPageMiddlewareWebSite\\ErrorPageMiddlewareWebSite.csproj", + "test\\WebSites\\XmlFormattersWebSite\\XmlFormattersWebSite.csproj", + "test\\WebSites\\ControllersFromServicesWebSite\\ControllersFromServicesWebSite.csproj", + "test\\WebSites\\ControllersFromServicesClassLibrary\\ControllersFromServicesClassLibrary.csproj", + "test\\WebSites\\CorsWebSite\\CorsWebSite.csproj", + "samples\\MvcSandbox\\MvcSandbox.csproj", + "test\\WebSites\\SimpleWebSite\\SimpleWebSite.csproj", + "test\\WebSites\\SecurityWebSite\\SecurityWebSite.csproj", + "test\\WebSites\\RazorPagesWebSite\\RazorPagesWebSite.csproj", + "benchmarks\\Microsoft.AspNetCore.Mvc.Performance\\Microsoft.AspNetCore.Mvc.Performance.csproj", + "test\\WebSites\\RazorBuildWebSite\\RazorBuildWebSite.csproj", + "test\\WebSites\\RazorBuildWebSite.Views\\RazorBuildWebSite.Views.csproj", + "Mvc.Analyzers\\src\\Microsoft.AspNetCore.Mvc.Analyzers.csproj", + "Mvc.Analyzers\\test\\Mvc.Analyzers.Test.csproj", + "test\\WebSites\\RazorPagesClassLibrary\\RazorPagesClassLibrary.csproj", + "shared\\Mvc.Views.TestCommon\\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", + "Mvc.Api.Analyzers\\test\\Mvc.Api.Analyzers.Test.csproj", + "Mvc.Api.Analyzers\\src\\Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj", + "test\\WebSites\\GenericHostWebSite\\GenericHostWebSite.csproj", + "Mvc\\src\\Microsoft.AspNetCore.Mvc.csproj", + "Mvc\\test\\Microsoft.AspNetCore.Mvc.Test.csproj", + "Mvc.Abstractions\\src\\Microsoft.AspNetCore.Mvc.Abstractions.csproj", + "Mvc.Abstractions\\test\\Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj", + "Mvc.ApiExplorer\\src\\Microsoft.AspNetCore.Mvc.ApiExplorer.csproj", + "Mvc.ApiExplorer\\test\\Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj", + "Mvc.Core\\src\\Microsoft.AspNetCore.Mvc.Core.csproj", + "Mvc.Core\\test\\Microsoft.AspNetCore.Mvc.Core.Test.csproj", + "Mvc.Cors\\src\\Microsoft.AspNetCore.Mvc.Cors.csproj", + "Mvc.Cors\\test\\Microsoft.AspNetCore.Mvc.Cors.Test.csproj", + "Mvc.DataAnnotations\\src\\Microsoft.AspNetCore.Mvc.DataAnnotations.csproj", + "Mvc.DataAnnotations\\test\\Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj", + "Mvc.Formatters.Json\\src\\Microsoft.AspNetCore.Mvc.Formatters.Json.csproj", + "Mvc.Formatters.Xml\\src\\Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj", + "Mvc.Formatters.Xml\\test\\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj", + "Mvc.Localization\\src\\Microsoft.AspNetCore.Mvc.Localization.csproj", + "Mvc.Localization\\test\\Microsoft.AspNetCore.Mvc.Localization.Test.csproj", + "Mvc.Razor\\src\\Microsoft.AspNetCore.Mvc.Razor.csproj", + "Mvc.Razor\\test\\Microsoft.AspNetCore.Mvc.Razor.Test.csproj", + "Mvc.RazorPages\\src\\Microsoft.AspNetCore.Mvc.RazorPages.csproj", + "Mvc.RazorPages\\test\\Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj", + "Mvc.TagHelpers\\src\\Microsoft.AspNetCore.Mvc.TagHelpers.csproj", + "Mvc.TagHelpers\\test\\Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj", + "Mvc.ViewFeatures\\src\\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj", + "Mvc.ViewFeatures\\test\\Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj", + "test\\Mvc.FunctionalTests\\Microsoft.AspNetCore.Mvc.FunctionalTests.csproj", + "test\\Mvc.IntegrationTests\\Microsoft.AspNetCore.Mvc.IntegrationTests.csproj", + "shared\\Mvc.TestDiagnosticListener\\Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj", + "Mvc.Testing\\src\\Microsoft.AspNetCore.Mvc.Testing.csproj", + "shared\\Mvc.Core.TestCommon\\Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj", + "Mvc.NewtonsoftJson\\src\\Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj", + "Mvc.NewtonsoftJson\\test\\Microsoft.AspNetCore.Mvc.NewtonsoftJson.Test.csproj", + "Mvc.Razor.RuntimeCompilation\\src\\Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.csproj", + "Mvc.Razor.RuntimeCompilation\\test\\Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.Test.csproj", + "test\\WebSites\\RazorBuildWebSite.PrecompiledViews\\RazorBuildWebSite.PrecompiledViews.csproj", + "Mvc.Components.Prerendering\\src\\Microsoft.AspNetCore.Mvc.Components.Prerendering.csproj", + "Mvc.Components.Prerendering\\test\\Microsoft.AspNetCore.Mvc.Components.Prerendering.Test.csproj", + ] + } +} From fdc056cebe69a1883b28d98d3a23064142364128 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 17 May 2019 15:12:24 -0700 Subject: [PATCH 2/2] Fixup --- src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs b/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs index bae4f65fd7..5bfe813d19 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/test/BsonTempDataSerializerTest.cs @@ -238,7 +238,6 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson [Fact] public void RoundTripTest_ListOfDateTime() { - // Documents the behavior of round-tripping a Guid value as a string // Arrange var key = "test-key"; var dateTime = new DateTime(2007, 1, 1);