From 6451d777f725b0f58fe58839d5955ed342b6b590 Mon Sep 17 00:00:00 2001 From: Doug Bunting <6431421+dougbu@users.noreply.github.com> Date: Mon, 6 Mar 2023 17:26:11 -0800 Subject: [PATCH] Throw `PlatformNotSupportedException` in `netstandard1.3` (#396) - stop trying to use DCS in the JSON and XML formatters - if `UseDataContractSerializer` or `!UseXmlSerializer`: - `CanReadType(...)` and `CanWriteType(...)` always return `false` - `ReadFromStreamAsync(...)` and `WriteToStreamAsync(...)` always `throw` - change default XML formatter configuration to use `XmlSerializer` - adjust and disable tests in reaction - add tests of new `netstandard1.3` behavior - nits: - correct namespace of `SerializerConsistencyTests`; was alone in previous namespace - s/DataContractFormatter/XmlSerializerFormatter/ to reflect actual `XmlMediaTypeFormatter` test actions --- .../Formatting/JsonMediaTypeFormatter.cs | 39 ++-- .../MediaTypeFormatterCollection.cs | 6 +- .../Formatting/XmlMediaTypeFormatter.cs | 22 ++- .../Properties/Resources.Designer.cs | 18 ++ .../Properties/Resources.resx | 6 + ...DataContractJsonMediaTypeFormatterTests.cs | 152 ++++++++++++++-- .../Formatting/JsonMediaTypeFormatterTests.cs | 2 + .../Formatting/MediaTypeFormatterTestBase.cs | 6 +- .../Formatting/SerializerConsistencyTests.cs | 16 +- .../Formatting/XmlMediaTypeFormatterTests.cs | 170 ++++++++++++++++-- .../HttpClientExtensionsTest.cs | 8 + 11 files changed, 392 insertions(+), 53 deletions(-) diff --git a/src/System.Net.Http.Formatting/Formatting/JsonMediaTypeFormatter.cs b/src/System.Net.Http.Formatting/Formatting/JsonMediaTypeFormatter.cs index 775a78a32..a3bb36060 100644 --- a/src/System.Net.Http.Formatting/Formatting/JsonMediaTypeFormatter.cs +++ b/src/System.Net.Http.Formatting/Formatting/JsonMediaTypeFormatter.cs @@ -6,7 +6,9 @@ using System.Diagnostics.Contracts; using System.IO; using System.Net.Http.Headers; +#if !NETSTANDARD1_3 // Unnecessary when targeting netstandard1.3. using System.Net.Http.Internal; +#endif using System.Runtime.Serialization.Json; using System.Text; using System.Threading; @@ -214,20 +216,15 @@ public override object ReadFromStream(Type type, Stream readStream, Encoding eff { DataContractJsonSerializer dataContractSerializer = GetDataContractSerializer(type); - // JsonReaderWriterFactory is internal, CreateTextReader only supports auto-detecting the encoding - // and auto-detection fails in some cases for the netstandard1.3 project. In addition, DCS encodings are - // limited to UTF8, UTF16BE, and UTF16LE. Convert to UTF8 as we read. - Stream innerStream = string.Equals(effectiveEncoding.WebName, Utf8Encoding.WebName, StringComparison.OrdinalIgnoreCase) ? +#if NETSTANDARD1_3 // Unreachable when targeting netstandard1.3. Return just to satisfy the compiler. + return null; +#else + // DCS encodings are limited to UTF8, UTF16BE, and UTF16LE. Convert to UTF8 as we read. + Stream innerStream = + string.Equals(effectiveEncoding.WebName, Utf8Encoding.WebName, StringComparison.OrdinalIgnoreCase) ? new NonClosingDelegatingStream(readStream) : new TranscodingStream(readStream, effectiveEncoding, Utf8Encoding, leaveOpen: true); -#if NETSTANDARD1_3 - using (innerStream) - { - // Unfortunately, we're ignoring _readerQuotas. - return dataContractSerializer.ReadObject(innerStream); - } -#else // XmlDictionaryReader will always dispose of innerStream when we dispose of the reader. using XmlDictionaryReader reader = JsonReaderWriterFactory.CreateJsonReader(innerStream, Utf8Encoding, _readerQuotas, onClose: null); @@ -314,10 +311,8 @@ private void WriteObject(Stream stream, Type type, object value) { DataContractJsonSerializer dataContractSerializer = GetDataContractSerializer(type); +#if !NETSTANDARD1_3 // Unreachable when targeting netstandard1.3. // Do not dispose of the stream. WriteToStream handles that where it's needed. -#if NETSTANDARD1_3 - dataContractSerializer.WriteObject(stream, value); -#else using XmlWriter writer = JsonReaderWriterFactory.CreateJsonWriter(stream, Utf8Encoding, ownsStream: false); dataContractSerializer.WriteObject(writer, value); #endif @@ -328,15 +323,26 @@ private DataContractJsonSerializer CreateDataContractSerializer(Type type, bool { Contract.Assert(type != null); +#if NETSTANDARD1_3 // XsdDataContractExporter is not supported in netstandard1.3 + if (throwOnError) + { + throw new PlatformNotSupportedException(Error.Format( + Properties.Resources.JsonMediaTypeFormatter_DCS_NotSupported, + nameof(UseDataContractJsonSerializer))); + } + else + { + return null; + } +#else + DataContractJsonSerializer serializer = null; Exception exception = null; try { -#if !NETSTANDARD1_3 // XsdDataContractExporter is not supported in netstandard1.3 // Verify that type is a valid data contract by forcing the serializer to try to create a data contract FormattingUtilities.XsdDataContractExporter.GetRootElementName(type); -#endif serializer = CreateDataContractSerializer(type); } @@ -362,6 +368,7 @@ private DataContractJsonSerializer CreateDataContractSerializer(Type type, bool } return serializer; +#endif } /// diff --git a/src/System.Net.Http.Formatting/Formatting/MediaTypeFormatterCollection.cs b/src/System.Net.Http.Formatting/Formatting/MediaTypeFormatterCollection.cs index 4320d1ff6..864eacded 100644 --- a/src/System.Net.Http.Formatting/Formatting/MediaTypeFormatterCollection.cs +++ b/src/System.Net.Http.Formatting/Formatting/MediaTypeFormatterCollection.cs @@ -255,7 +255,11 @@ private static IEnumerable CreateDefaultFormatters() return new MediaTypeFormatter[] { new JsonMediaTypeFormatter(), - new XmlMediaTypeFormatter(), + new XmlMediaTypeFormatter() +#if NETSTANDARD1_3 // XsdDataContractExporter is not supported in netstandard1.3. Cannot use DCS. + { UseXmlSerializer = true } +#endif + , new FormUrlEncodedMediaTypeFormatter() }; } diff --git a/src/System.Net.Http.Formatting/Formatting/XmlMediaTypeFormatter.cs b/src/System.Net.Http.Formatting/Formatting/XmlMediaTypeFormatter.cs index 77a68d69b..76ee18f3d 100644 --- a/src/System.Net.Http.Formatting/Formatting/XmlMediaTypeFormatter.cs +++ b/src/System.Net.Http.Formatting/Formatting/XmlMediaTypeFormatter.cs @@ -511,6 +511,23 @@ public object InvokeGetSerializer(Type type, object value, HttpContent content) private object CreateDefaultSerializer(Type type, bool throwOnError) { Contract.Assert(type != null, "type cannot be null."); + +#if NETSTANDARD1_3 // XsdDataContractExporter is not supported in netstandard1.3 + if (!UseXmlSerializer) + { + if (throwOnError) + { + throw new PlatformNotSupportedException(Error.Format( + Properties.Resources.XmlMediaTypeFormatter_DCS_NotSupported, + nameof(UseXmlSerializer))); + } + else + { + return null; + } + } +#endif + Exception exception = null; object serializer = null; @@ -522,13 +539,12 @@ private object CreateDefaultSerializer(Type type, bool throwOnError) } else { -#if !NETSTANDARD1_3 // XsdDataContractExporter is not supported in netstandard1.3 - // REVIEW: Is there something comparable in WinRT? +#if !NETSTANDARD1_3 // Unreachable when targeting netstandard1.3. // Verify that type is a valid data contract by forcing the serializer to try to create a data contract FormattingUtilities.XsdDataContractExporter.GetRootElementName(type); -#endif serializer = CreateDataContractSerializer(type); +#endif } } catch (Exception caught) diff --git a/src/System.Net.Http.Formatting/Properties/Resources.Designer.cs b/src/System.Net.Http.Formatting/Properties/Resources.Designer.cs index 7a0bd97ba..88d4eefbc 100644 --- a/src/System.Net.Http.Formatting/Properties/Resources.Designer.cs +++ b/src/System.Net.Http.Formatting/Properties/Resources.Designer.cs @@ -390,6 +390,15 @@ internal static string JQuery13CompatModeNotSupportNestedJson { } } + /// + /// Looks up a localized string similar to Unable to validate types on this platform when {0} is 'true'. Please reset {0} or move to a supported platform, one where the 'netstandard2.0' assembly is usable.. + /// + internal static string JsonMediaTypeFormatter_DCS_NotSupported { + get { + return ResourceManager.GetString("JsonMediaTypeFormatter_DCS_NotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to The '{0}' method returned null. It must return a JSON serializer instance.. /// @@ -768,6 +777,15 @@ internal static string UnsupportedIndent { } } + /// + /// Looks up a localized string similar to Unable to validate types on this platform when {0} is 'false'. Please set {0} or move to a supported platform, one where the 'netstandard2.0' assembly is usable.. + /// + internal static string XmlMediaTypeFormatter_DCS_NotSupported { + get { + return ResourceManager.GetString("XmlMediaTypeFormatter_DCS_NotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to The object of type '{0}' returned by {1} must be an instance of either XmlObjectSerializer or XmlSerializer.. /// diff --git a/src/System.Net.Http.Formatting/Properties/Resources.resx b/src/System.Net.Http.Formatting/Properties/Resources.resx index 1b8df2cd2..ef5c4a7db 100644 --- a/src/System.Net.Http.Formatting/Properties/Resources.resx +++ b/src/System.Net.Http.Formatting/Properties/Resources.resx @@ -357,4 +357,10 @@ Cannot access a closed stream. + + Unable to validate types on this platform when {0} is 'true'. Please reset {0} or move to a supported platform, one where the 'netstandard2.0' assembly is usable. + + + Unable to validate types on this platform when {0} is 'false'. Please set {0} or move to a supported platform, one where the 'netstandard2.0' assembly is usable. + \ No newline at end of file diff --git a/test/System.Net.Http.Formatting.Test/Formatting/DataContractJsonMediaTypeFormatterTests.cs b/test/System.Net.Http.Formatting.Test/Formatting/DataContractJsonMediaTypeFormatterTests.cs index 1d3d1999a..7ad93ee2a 100644 --- a/test/System.Net.Http.Formatting.Test/Formatting/DataContractJsonMediaTypeFormatterTests.cs +++ b/test/System.Net.Http.Formatting.Test/Formatting/DataContractJsonMediaTypeFormatterTests.cs @@ -24,6 +24,13 @@ public DataContractJsonMediaTypeFormatter() public class DataContractJsonMediaTypeFormatterTests : MediaTypeFormatterTestBase { + public static readonly TheoryDataSet AFewValidTypes = new() + { + typeof(bool), + typeof(int), + typeof(string), + }; + public override IEnumerable ExpectedSupportedMediaTypes { get { return HttpTestData.StandardJsonMediaTypes; } @@ -87,21 +94,20 @@ public void CanReadType_ReturnsExpectedValues(Type variationType, object testDat Assert.False(isSerializable != canSupport && isSerializable, String.Format("2nd CanReadType returned wrong value for '{0}'.", variationType)); } -#if !Testing_NetStandard1_3 // XsdDataContractExporterMethods unconditionally return true without XsdDataContractExporter to use. - [Fact] - public void CanReadType_ReturnsFalse_ForInvalidDataContracts() + [Theory] + [PropertyData(nameof(AFewValidTypes))] + public void CanWriteType_ReturnsFalse_ForValidTypes(Type type) { - JsonMediaTypeFormatter formatter = new DataContractJsonMediaTypeFormatter(); - Assert.False(formatter.CanReadType(typeof(InvalidDataContract))); - } + XmlMediaTypeFormatter formatter = new(); - [Fact] - public void CanWriteType_ReturnsFalse_ForInvalidDataContracts() - { - JsonMediaTypeFormatter formatter = new DataContractJsonMediaTypeFormatter(); - Assert.False(formatter.CanWriteType(typeof(InvalidDataContract))); - } + var canWrite = formatter.CanWriteType(type); + +#if Testing_NetStandard1_3 // Different behavior in netstandard1.3 due to no DataContract validation. + Assert.False(canWrite); +#else + Assert.True(canWrite); #endif + } public class InvalidDataContract { @@ -111,6 +117,7 @@ public InvalidDataContract(string s) } } +#if !Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. [Theory] [InlineData(typeof(IQueryable))] [InlineData(typeof(IEnumerable))] @@ -124,6 +131,7 @@ public async Task UseJsonFormatterWithNull(Type type) string serializedString = new StreamReader(memoryStream).ReadToEnd(); Assert.True(serializedString.Contains("null"), "Using Json formatter to serialize null should emit 'null'."); } +#endif [Theory] [TestDataSet(typeof(JsonMediaTypeFormatterTests), "ValueAndRefTypeTestDataCollectionExceptULong", RoundTripDataVariations)] @@ -159,6 +167,54 @@ public async Task ReadFromStreamAsync_RoundTripsWriteToStreamAsync_KnownTypes(Ty } } +#if Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. + [Theory] + [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection", RoundTripDataVariations)] + public async Task ReadFromStreamAsync_UsingDataContractSerializer_Throws(Type variationType, object testData) + { + // Arrange. First, get some data using XmlSerializer. + bool canSerialize = IsTypeSerializableWithJsonSerializer(variationType, testData, actuallyCheck: true) && + Assert.Http.CanRoundTrip(variationType); + if (canSerialize) + { + var formatter = new JsonMediaTypeFormatter(); + using var stream = new MemoryStream(); + using var content = new StringContent(string.Empty); + + await formatter.WriteToStreamAsync(variationType, testData, stream, content, transportContext: null); + await stream.FlushAsync(); + stream.Position = 0L; + + content.Headers.ContentLength = stream.Length; + formatter.UseDataContractJsonSerializer = true; + + // Act & Assert + await Assert.ThrowsAsync(() => + formatter.ReadFromStreamAsync(variationType, stream, content, formatterLogger: null), + "Unable to validate types on this platform when UseDataContractJsonSerializer is 'true'. " + + "Please reset UseDataContractJsonSerializer or move to a supported platform, one where the " + + "'netstandard2.0' assembly is usable."); + } + } + + [Theory] + [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection", RoundTripDataVariations)] + public async Task WriteToStreamAsync_UsingDataContractSerializer_Throws(Type variationType, object testData) + { + // Arrange + var formatter = new JsonMediaTypeFormatter() { UseDataContractJsonSerializer = true}; + using var stream = new MemoryStream(); + using var content = new StringContent(string.Empty); + + // Act & Assert + await Assert.ThrowsAsync(() => + formatter.WriteToStreamAsync(variationType, testData, stream, content, transportContext: null), + "Unable to validate types on this platform when UseDataContractJsonSerializer is 'true'. " + + "Please reset UseDataContractJsonSerializer or move to a supported platform, one where the " + + "'netstandard2.0' assembly is usable."); + } + +#else #if !NETCOREAPP2_1 // DBNull not serializable on .NET Core 2.1. // Test alternate null value [Fact] @@ -203,6 +259,7 @@ public async Task UseDataContractJsonSerializer_Default() string serializedString = new StreamReader(memoryStream).ReadToEnd(); Assert.False(serializedString.Contains("\r\n"), "Using DCJS should emit data without indentation by default."); } +#endif [Fact] public void UseDataContractJsonSerializer_True_Indent_Throws() @@ -216,6 +273,67 @@ public void UseDataContractJsonSerializer_True_Indent_Throws() memoryStream, content, transportContext: null)); } +#if Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. + [Fact] + public override Task Overridden_ReadFromStreamAsyncWithCancellationToken_GetsCalled() + { + return Task.CompletedTask; + } + + [Fact] + public override Task Overridden_ReadFromStreamAsyncWithoutCancellationToken_GetsCalled() + { + return Task.CompletedTask; + } + + [Fact] + public override Task Overridden_WriteToStreamAsyncWithCancellationToken_GetsCalled() + { + return Task.CompletedTask; + } + + [Fact] + public override Task Overridden_WriteToStreamAsyncWithoutCancellationToken_GetsCalled() + { + return Task.CompletedTask; + } + + [Fact] + public override Task ReadFromStreamAsync_ReadsDataButDoesNotCloseStream() + { + return Task.CompletedTask; + } + + // Attributes are in base class. + public override Task ReadFromStreamAsync_UsesCorrectCharacterEncoding(string content, string encoding, bool isDefaultEncoding) + { + return Task.CompletedTask; + } + + [Fact] + public override Task ReadFromStreamAsync_WhenContentLengthIsNull_ReadsDataButDoesNotCloseStream() + { + return Task.CompletedTask; + } + + // Attributes are in base class. + public override Task WriteToStreamAsync_UsesCorrectCharacterEncoding(string content, string encoding, bool isDefaultEncoding) + { + return Task.CompletedTask; + } + + [Fact] + public override Task WriteToStreamAsync_WhenObjectIsNull_WritesDataButDoesNotCloseStream() + { + return Task.CompletedTask; + } + + [Fact] + public override Task WriteToStreamAsync_WritesDataButDoesNotCloseStream() + { + return Task.CompletedTask; + } +#else public override Task ReadFromStreamAsync_UsesCorrectCharacterEncoding(string content, string encoding, bool isDefaultEncoding) { // Arrange @@ -239,6 +357,7 @@ public override Task WriteToStreamAsync_UsesCorrectCharacterEncoding(string cont return WriteContentUsingCorrectCharacterEncodingHelperAsync( formatter, content, formattedContent, mediaType, encoding, isDefaultEncoding); } +#endif public class TestJsonMediaTypeFormatter : DataContractJsonMediaTypeFormatter { @@ -267,8 +386,15 @@ public override DataContractJsonSerializer CreateDataContractSerializer(Type typ } } - private bool IsTypeSerializableWithJsonSerializer(Type type, object obj) + private bool IsTypeSerializableWithJsonSerializer(Type type, object obj, bool actuallyCheck = false) { +#if Testing_NetStandard1_3 // Different behavior in netstandard1.3 due to no DataContract validation. + if (!actuallyCheck) + { + return false; + } +#endif + try { new DataContractJsonSerializer(type); diff --git a/test/System.Net.Http.Formatting.Test/Formatting/JsonMediaTypeFormatterTests.cs b/test/System.Net.Http.Formatting.Test/Formatting/JsonMediaTypeFormatterTests.cs index 26d85ec46..31de4facd 100644 --- a/test/System.Net.Http.Formatting.Test/Formatting/JsonMediaTypeFormatterTests.cs +++ b/test/System.Net.Http.Formatting.Test/Formatting/JsonMediaTypeFormatterTests.cs @@ -225,6 +225,7 @@ public async Task FormatterThrowsOnReadWhenOverridenCreateReturnsNull() Assert.NotNull(formatter.InnerJsonSerializer); } +#if !Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. [Fact] public async Task DataContractFormatterThrowsOnWriteWhenOverridenCreateFails() { @@ -309,6 +310,7 @@ public async Task DataContractFormatterThrowsOnReadWhenOverridenCreateReturnsNul Assert.NotNull(formatter.InnerDataContractSerializer); Assert.Null(formatter.InnerJsonSerializer); } +#endif [Fact] public void CanReadType_ReturnsTrueOnJtoken() diff --git a/test/System.Net.Http.Formatting.Test/Formatting/MediaTypeFormatterTestBase.cs b/test/System.Net.Http.Formatting.Test/Formatting/MediaTypeFormatterTestBase.cs index d5618f8db..b3c4e4831 100644 --- a/test/System.Net.Http.Formatting.Test/Formatting/MediaTypeFormatterTestBase.cs +++ b/test/System.Net.Http.Formatting.Test/Formatting/MediaTypeFormatterTestBase.cs @@ -152,7 +152,7 @@ public async Task ReadFromStreamAsync_WhenContentLengthIsZero_ReturnsDefaultType } [Fact] - public async Task ReadFromStreamAsync_ReadsDataButDoesNotCloseStream() + public virtual async Task ReadFromStreamAsync_ReadsDataButDoesNotCloseStream() { // Arrange TFormatter formatter = CreateFormatter(); @@ -173,7 +173,7 @@ public async Task ReadFromStreamAsync_ReadsDataButDoesNotCloseStream() } [Fact] - public async Task ReadFromStreamAsync_WhenContentLengthIsNull_ReadsDataButDoesNotCloseStream() + public virtual async Task ReadFromStreamAsync_WhenContentLengthIsNull_ReadsDataButDoesNotCloseStream() { // Arrange TFormatter formatter = CreateFormatter(); @@ -219,7 +219,7 @@ public virtual async Task WriteToStreamAsync_WhenObjectIsNull_WritesDataButDoesN } [Fact] - public async Task WriteToStreamAsync_WritesDataButDoesNotCloseStream() + public virtual async Task WriteToStreamAsync_WritesDataButDoesNotCloseStream() { // Arrange TFormatter formatter = CreateFormatter(); diff --git a/test/System.Net.Http.Formatting.Test/Formatting/SerializerConsistencyTests.cs b/test/System.Net.Http.Formatting.Test/Formatting/SerializerConsistencyTests.cs index 63dec00a3..a2ae6aa7b 100644 --- a/test/System.Net.Http.Formatting.Test/Formatting/SerializerConsistencyTests.cs +++ b/test/System.Net.Http.Formatting.Test/Formatting/SerializerConsistencyTests.cs @@ -4,12 +4,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net.Http.Formatting; using System.Runtime.Serialization; using System.Threading.Tasks; using Microsoft.TestCommon; -namespace System.Net.Formatting.Tests +namespace System.Net.Http.Formatting { // Tests for ensuring the serializers behave consistently in various cases. // This is important for conneg. @@ -18,7 +17,12 @@ public class SerializerConsistencyTests [Fact] public Task PartialContract() { - var c = new PartialDataContract { PropertyWithAttribute = "one", PropertyWithoutAttribute = "false" }; + var c = new PartialDataContract { + PropertyWithAttribute = "one", +#if !Testing_NetStandard1_3 // Xml formatter ignores DCS attributes but JSON one does not in netstandard1.3. + PropertyWithoutAttribute = "false" +#endif + }; return SerializerConsistencyHepers.TestAsync(c); } @@ -62,6 +66,7 @@ public Task NullEmptyWhitespaceString() return SerializerConsistencyHepers.TestAsync(source); } +#if !Testing_NetStandard1_3 // XmlSerializer is unable to write XML for a dictionary. [Fact] public Task Dictionary() { @@ -71,6 +76,7 @@ public Task Dictionary() return SerializerConsistencyHepers.TestAsync(dict); } +#endif [Fact] public Task Array() @@ -80,6 +86,7 @@ public Task Array() return SerializerConsistencyHepers.TestAsync(array); } +#if !Testing_NetStandard1_3 // XmlSerializer is unable to read XML for interfaces. [Fact] public async Task ArrayInterfaces() { @@ -99,6 +106,7 @@ public Task Linq() // So explicitly call out IEnumerable return SerializerConsistencyHepers.TestAsync(l, typeof(IEnumerable)); } +#endif [Fact] public Task StaticProps() @@ -141,8 +149,10 @@ public class PartialDataContract [DataMember] public string PropertyWithAttribute { get; set; } +#if !Testing_NetStandard1_3 // Xml formatter ignores DCS attributes but JSON one does not in netstandard1.3. // no attribute here public string PropertyWithoutAttribute { get; set; } +#endif } public class PrivateProperty // with private field diff --git a/test/System.Net.Http.Formatting.Test/Formatting/XmlMediaTypeFormatterTests.cs b/test/System.Net.Http.Formatting.Test/Formatting/XmlMediaTypeFormatterTests.cs index 28b73acfd..c83f687ec 100644 --- a/test/System.Net.Http.Formatting.Test/Formatting/XmlMediaTypeFormatterTests.cs +++ b/test/System.Net.Http.Formatting.Test/Formatting/XmlMediaTypeFormatterTests.cs @@ -41,6 +41,13 @@ public class XmlMediaTypeFormatterTests : MediaTypeFormatterTestBase AFewValidTypes = new() + { + typeof(bool), + typeof(int), + typeof(string), + }; + public static IEnumerable BunchOfTypedObjectsTestDataCollection { get { return new TestData[] { BunchOfTypedObjectsTestData, }; } @@ -100,6 +107,7 @@ public void MaxDepthReturnsCorrectValue() roundTripTestValue: 10); } +#if !Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. [Fact] public async Task ReadDeeplyNestedObjectThrows() { @@ -110,6 +118,7 @@ public async Task ReadDeeplyNestedObjectThrows() stream.Position = 0; await Assert.ThrowsAsync(() => formatter.ReadFromStreamAsync(typeof(SampleType), stream, null, null)); } +#endif [Fact] public void Indent_RoundTrips() @@ -129,6 +138,7 @@ public void UseXmlSerializer_RoundTrips() expectedDefaultValue: false); } +#if !Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. [Theory] [InlineData(typeof(IEnumerable))] [InlineData(typeof(IQueryable))] @@ -173,6 +183,7 @@ public async Task UseXmlSerializer_False_Indent() string serializedString = new StreamReader(memoryStream).ReadToEnd(); Assert.True(serializedString.Contains("\r\n"), "Using DCS with indent set to true should emit data with indentation."); } +#endif [Fact] public void SetSerializer_ThrowsWithNullType() @@ -225,6 +236,7 @@ public void RemoveSerializer_ThrowsWithNullType() Assert.ThrowsArgumentNull(() => { formatter.RemoveSerializer(null); }, "type"); } +#if !Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. [Fact] public async Task FormatterThrowsOnWriteWhenOverridenCreateFails() { @@ -305,9 +317,10 @@ public async Task FormatterThrowsOnReadWhenOverridenCreateReturnsNull() Assert.NotNull(formatter.InnerDataContractSerializer); Assert.Null(formatter.InnerXmlSerializer); } +#endif [Fact] - public async Task DataContractFormatterThrowsOnWriteWhenOverridenCreateFails() + public async Task XmlSerializerFormatterThrowsOnWriteWhenOverridenCreateFails() { // Arrange TestXmlMediaTypeFormatter formatter = new TestXmlMediaTypeFormatter(); @@ -327,7 +340,7 @@ public async Task DataContractFormatterThrowsOnWriteWhenOverridenCreateFails() } [Fact] - public async Task DataContractFormatterThrowsOnWriteWhenOverridenCreateReturnsNull() + public async Task XmlSerializerFormatterThrowsOnWriteWhenOverridenCreateReturnsNull() { // Arrange TestXmlMediaTypeFormatter formatter = new TestXmlMediaTypeFormatter(); @@ -347,7 +360,7 @@ public async Task DataContractFormatterThrowsOnWriteWhenOverridenCreateReturnsNu } [Fact] - public async Task DataContractFormatterThrowsOnReadWhenOverridenCreateFails() + public async Task XmlSerializerFormatterThrowsOnReadWhenOverridenCreateFails() { // Arrange TestXmlMediaTypeFormatter formatter = new TestXmlMediaTypeFormatter(); @@ -369,7 +382,7 @@ public async Task DataContractFormatterThrowsOnReadWhenOverridenCreateFails() } [Fact] - public async Task DataContractFormatterThrowsOnReadWhenOverridenCreateReturnsNull() + public async Task XmlSerializerFormatterThrowsOnReadWhenOverridenCreateReturnsNull() { // Arrange TestXmlMediaTypeFormatter formatter = new TestXmlMediaTypeFormatter(); @@ -478,6 +491,54 @@ public async Task ReadFromStream_AsyncRoundTripsWriteToStreamUsingDataContractSe } } +#if Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. + [Theory] + [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection", RoundTripDataVariations)] + public async Task ReadFromStreamAsync_UsingDataContractSerializer_Throws(Type variationType, object testData) + { + // Arrange. First, get some data using XmlSerializer. + bool canSerialize = IsSerializableWithXmlSerializer(variationType, testData) && Assert.Http.CanRoundTrip(variationType); + if (canSerialize) + { + var formatter = new XmlMediaTypeFormatter() { UseXmlSerializer = true }; + using var stream = new MemoryStream(); + using var content = new StringContent(string.Empty); + + await formatter.WriteToStreamAsync(variationType, testData, stream, content, transportContext: null); + await stream.FlushAsync(); + stream.Position = 0L; + + content.Headers.ContentLength = stream.Length; + formatter.RemoveSerializer(variationType); + formatter.UseXmlSerializer = false; + + // Act & Assert + await Assert.ThrowsAsync(() => + formatter.ReadFromStreamAsync(variationType, stream, content, formatterLogger: null), + "Unable to validate types on this platform when UseXmlSerializer is 'false'. Please set " + + "UseXmlSerializer or move to a supported platform, one where the 'netstandard2.0' assembly " + + "is usable."); + } + } + + [Theory] + [TestDataSet(typeof(CommonUnitTestDataSets), "RepresentativeValueAndRefTypeTestDataCollection", RoundTripDataVariations)] + public async Task WriteToStreamAsync_UsingDataContractSerializer_Throws(Type variationType, object testData) + { + // Arrange + var formatter = new XmlMediaTypeFormatter(); + using var stream = new MemoryStream(); + using var content = new StringContent(string.Empty); + + // Act & Assert + await Assert.ThrowsAsync(() => + formatter.WriteToStreamAsync(variationType, testData, stream, content, transportContext: null), + "Unable to validate types on this platform when UseXmlSerializer is 'false'. Please set " + + "UseXmlSerializer or move to a supported platform, one where the 'netstandard2.0' assembly " + + "is usable."); + } + +#else #if !NETCOREAPP2_1 // DBNull not serializable on .NET Core 2.1. [Fact] public async Task ReadFromStreamAsync_RoundTripsWriteToStreamAsyncUsingDataContractSerializer_DBNull() @@ -523,6 +584,7 @@ public override Task ReadFromStreamAsync_UsesCorrectCharacterEncoding(string con // Act & assert return ReadFromStreamAsync_UsesCorrectCharacterEncodingHelper(formatter, content, formattedContent, mediaType, encoding, isDefaultEncoding); } +#endif [Fact] public async Task ReadFromStreamAsync_UsesGetDeserializerAndCreateXmlReader() @@ -567,6 +629,7 @@ public Task ReadFromStreamAsync_ThrowsException_WhenGetDeserializerReturnsInvali "The object of type 'JsonSerializer' returned by GetDeserializer must be an instance of either XmlObjectSerializer or XmlSerializer."); } +#if !Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. public override Task WriteToStreamAsync_UsesCorrectCharacterEncoding(string content, string encoding, bool isDefaultEncoding) { // Arrange @@ -578,6 +641,7 @@ public override Task WriteToStreamAsync_UsesCorrectCharacterEncoding(string cont // Act & assert return WriteToStreamAsync_UsesCorrectCharacterEncodingHelper(formatter, content, formattedContent, mediaType, encoding, isDefaultEncoding); } +#endif [Fact] public async Task WriteToStreamAsync_UsesGetSerializerAndCreateXmlWriter() @@ -650,6 +714,7 @@ public void Property_WriterSettings_DefaultValues() Assert.False(formatter.WriterSettings.CheckCharacters); } +#if !Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. [Fact] public async Task InvalidXmlCharacters_CanBeSerialized_Default() { @@ -672,8 +737,38 @@ public Task InvalidXmlCharacters_CannotBeSerialized_IfCheckCharactersIsTrue() () => formatter.WriteToStreamAsync(typeof(string), "\x16", stream, content, null), "'\x16', hexadecimal value 0x16, is an invalid character."); } +#endif + + [Theory] + [PropertyData(nameof(AFewValidTypes))] + public void CanReadType_ReturnsFalse_ForValidTypes(Type type) + { + XmlMediaTypeFormatter formatter = new(); + + var canRead = formatter.CanReadType(type); + +#if Testing_NetStandard1_3 // Different behavior in netstandard1.3 due to no DataContract validation. + Assert.False(canRead); +#else + Assert.True(canRead); +#endif + } + + [Theory] + [PropertyData(nameof(AFewValidTypes))] + public void CanWriteType_ReturnsFalse_ForValidTypes(Type type) + { + XmlMediaTypeFormatter formatter = new(); + + var canWrite = formatter.CanWriteType(type); + +#if Testing_NetStandard1_3 // Different behavior in netstandard1.3 due to no DataContract validation. + Assert.False(canWrite); +#else + Assert.True(canWrite); +#endif + } -#if !Testing_NetStandard1_3 // Different behavior in netstandard1.3 due to no DataContract validation [Fact] public void CanReadType_ReturnsFalse_ForInvalidDataContracts() { @@ -689,23 +784,66 @@ public void CanWriteType_ReturnsFalse_ForInvalidDataContracts() Assert.False(formatter.CanWriteType(typeof(InvalidDataContract))); } -#else + +#if Testing_NetStandard1_3 // Cannot read or write w/ DCS in netstandard1.3. [Fact] - public void CanReadType_InPortableLibrary_ReturnsFalse_ForInvalidDataContracts() + public override Task Overridden_ReadFromStreamAsyncWithCancellationToken_GetsCalled() { - XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter(); + return Task.CompletedTask; + } - // The formatter is unable to positively identify non readable types, so true is always returned - Assert.True(formatter.CanReadType(typeof(InvalidDataContract))); + [Fact] + public override Task Overridden_ReadFromStreamAsyncWithoutCancellationToken_GetsCalled() + { + return Task.CompletedTask; } [Fact] - public void CanWriteType_InPortableLibrary_ReturnsTrue_ForInvalidDataContracts() + public override Task Overridden_WriteToStreamAsyncWithCancellationToken_GetsCalled() { - XmlMediaTypeFormatter formatter = new XmlMediaTypeFormatter(); + return Task.CompletedTask; + } + + [Fact] + public override Task Overridden_WriteToStreamAsyncWithoutCancellationToken_GetsCalled() + { + return Task.CompletedTask; + } - // The formatter is unable to positively identify non readable types, so true is always returned - Assert.True(formatter.CanWriteType(typeof(InvalidDataContract))); + [Fact] + public override Task ReadFromStreamAsync_ReadsDataButDoesNotCloseStream() + { + return Task.CompletedTask; + } + + // Attributes are in base class. + public override Task ReadFromStreamAsync_UsesCorrectCharacterEncoding(string content, string encoding, bool isDefaultEncoding) + { + return Task.CompletedTask; + } + + [Fact] + public override Task ReadFromStreamAsync_WhenContentLengthIsNull_ReadsDataButDoesNotCloseStream() + { + return Task.CompletedTask; + } + + // Attributes are in base class. + public override Task WriteToStreamAsync_UsesCorrectCharacterEncoding(string content, string encoding, bool isDefaultEncoding) + { + return Task.CompletedTask; + } + + [Fact] + public override Task WriteToStreamAsync_WhenObjectIsNull_WritesDataButDoesNotCloseStream() + { + return Task.CompletedTask; + } + + [Fact] + public override Task WriteToStreamAsync_WritesDataButDoesNotCloseStream() + { + return Task.CompletedTask; } #endif @@ -803,6 +941,9 @@ private bool IsSerializableWithXmlSerializer(Type type, object obj) private bool IsSerializableWithDataContractSerializer(Type type, object obj) { +#if Testing_NetStandard1_3 // Different behavior in netstandard1.3 due to no DataContract validation. + return false; +#else if (Assert.Http.IsKnownUnserializable(type, obj)) { return false; @@ -822,6 +963,7 @@ private bool IsSerializableWithDataContractSerializer(Type type, object obj) } return true; +#endif } } } diff --git a/test/System.Net.Http.Formatting.Test/HttpClientExtensionsTest.cs b/test/System.Net.Http.Formatting.Test/HttpClientExtensionsTest.cs index 5007ff4ac..2e6dc1d73 100644 --- a/test/System.Net.Http.Formatting.Test/HttpClientExtensionsTest.cs +++ b/test/System.Net.Http.Formatting.Test/HttpClientExtensionsTest.cs @@ -66,6 +66,7 @@ public void PostAsXmlAsync_String_WhenClientIsNull_ThrowsException() Assert.ThrowsArgumentNull(() => client.PostAsXmlAsync("http://www.example.com", new object()), "client"); } +#if !Testing_NetStandard1_3 // Avoid "The configured formatter 'System.Net.Http.Formatting.XmlMediaTypeFormatter' cannot write an object of type 'Object'." [Fact] public void PostAsXmlAsync_String_WhenUriIsNull_ThrowsException() { @@ -81,6 +82,7 @@ public async Task PostAsXmlAsync_String_UsesXmlMediaTypeFormatter() var content = Assert.IsType>(response.RequestMessage.Content); Assert.IsType(content.Formatter); } +#endif [Fact] public void PostAsync_String_WhenClientIsNull_ThrowsException() @@ -195,6 +197,7 @@ public void PutAsXmlAsync_String_WhenClientIsNull_ThrowsException() Assert.ThrowsArgumentNull(() => client.PutAsXmlAsync("http://www.example.com", new object()), "client"); } +#if !Testing_NetStandard1_3 // Avoid "The configured formatter 'System.Net.Http.Formatting.XmlMediaTypeFormatter' cannot write an object of type 'Object'." [Fact] public void PutAsXmlAsync_String_WhenUriIsNull_ThrowsException() { @@ -210,6 +213,7 @@ public async Task PutAsXmlAsync_String_UsesXmlMediaTypeFormatter() var content = Assert.IsType>(response.RequestMessage.Content); Assert.IsType(content.Formatter); } +#endif [Fact] public void PutAsync_String_WhenClientIsNull_ThrowsException() @@ -324,6 +328,7 @@ public void PostAsXmlAsync_Uri_WhenClientIsNull_ThrowsException() Assert.ThrowsArgumentNull(() => client.PostAsXmlAsync(new Uri("http://www.example.com"), new object()), "client"); } +#if !Testing_NetStandard1_3 // Avoid "The configured formatter 'System.Net.Http.Formatting.XmlMediaTypeFormatter' cannot write an object of type 'Object'." [Fact] public void PostAsXmlAsync_Uri_WhenUriIsNull_ThrowsException() { @@ -339,6 +344,7 @@ public async Task PostAsXmlAsync_Uri_UsesXmlMediaTypeFormatter() var content = Assert.IsType>(response.RequestMessage.Content); Assert.IsType(content.Formatter); } +#endif [Fact] public void PostAsync_Uri_WhenClientIsNull_ThrowsException() @@ -453,6 +459,7 @@ public void PutAsXmlAsync_Uri_WhenClientIsNull_ThrowsException() Assert.ThrowsArgumentNull(() => client.PutAsXmlAsync(new Uri("http://www.example.com"), new object()), "client"); } +#if !Testing_NetStandard1_3 // Avoid "The configured formatter 'System.Net.Http.Formatting.XmlMediaTypeFormatter' cannot write an object of type 'Object'." [Fact] public void PutAsXmlAsync_Uri_WhenUriIsNull_ThrowsException() { @@ -468,6 +475,7 @@ public async Task PutAsXmlAsync_Uri_UsesXmlMediaTypeFormatter() var content = Assert.IsType>(response.RequestMessage.Content); Assert.IsType(content.Formatter); } +#endif [Fact] public void PutAsync_Uri_WhenClientIsNull_ThrowsException()