diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs index d605a025b8..ae71646600 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal options.ModelMetadataDetailsProviders.Add(new DataMemberRequiredBindingMetadataProvider()); options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); - options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options.SuppressInputFormatterBuffering)); + options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options)); options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider("System.Xml.Linq.XObject")); options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider("System.Xml.XmlNode")); diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs index 1944408bbd..b072db2b12 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal public void Configure(MvcOptions options) { options.OutputFormatters.Add(new XmlSerializerOutputFormatter()); - options.InputFormatters.Add(new XmlSerializerInputFormatter(options.SuppressInputFormatterBuffering)); + options.InputFormatters.Add(new XmlSerializerInputFormatter(options)); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs index e2dde401b1..81f8c92873 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs @@ -28,24 +28,15 @@ namespace Microsoft.AspNetCore.Mvc.Formatters private readonly ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas(); private readonly bool _suppressInputFormatterBuffering; + private readonly MvcOptions _options; private DataContractSerializerSettings _serializerSettings; /// - /// Initializes a new instance of DataContractSerializerInputFormatter + /// Initializes a new instance of . /// - public XmlDataContractSerializerInputFormatter() : - this(suppressInputFormatterBuffering: false) + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public XmlDataContractSerializerInputFormatter() { - } - - /// - /// Initializes a new instance of DataContractSerializerInputFormatter - /// - /// Flag to buffer entire request body before deserializing it. - public XmlDataContractSerializerInputFormatter(bool suppressInputFormatterBuffering) - { - _suppressInputFormatterBuffering = suppressInputFormatterBuffering; - SupportedEncodings.Add(UTF8EncodingWithoutBOM); SupportedEncodings.Add(UTF16EncodingLittleEndian); @@ -59,6 +50,29 @@ namespace Microsoft.AspNetCore.Mvc.Formatters WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); } + /// + /// Initializes a new instance of . + /// + /// Flag to buffer entire request body before deserializing it. + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public XmlDataContractSerializerInputFormatter(bool suppressInputFormatterBuffering) + : this() + { + _suppressInputFormatterBuffering = suppressInputFormatterBuffering; + } + + /// + /// Initializes a new instance of . + /// + /// The . + public XmlDataContractSerializerInputFormatter(MvcOptions options) +#pragma warning disable CS0618 + : this() +#pragma warning restore CS0618 + { + _options = options; + } + /// /// Gets the list of to /// provide the wrapping type for de-serialization. @@ -126,7 +140,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters var request = context.HttpContext.Request; - if (!request.Body.CanSeek && !_suppressInputFormatterBuffering) + var suppressInputFormatterBuffering = _options?.SuppressInputFormatterBuffering ?? _suppressInputFormatterBuffering; + + if (!request.Body.CanSeek && !suppressInputFormatterBuffering) { // XmlDataContractSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs index 02cc381aaf..56980ea1ba 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs @@ -28,23 +28,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters private readonly ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas(); private readonly bool _suppressInputFormatterBuffering; + private readonly MvcOptions _options; /// /// Initializes a new instance of XmlSerializerInputFormatter. /// + [Obsolete("This constructor is obsolete and will be removed in a future version.")] public XmlSerializerInputFormatter() - : this(suppressInputFormatterBuffering: false) { - } - - /// - /// Initializes a new instance of XmlSerializerInputFormatter. - /// - /// Flag to buffer entire request body before deserializing it. - public XmlSerializerInputFormatter(bool suppressInputFormatterBuffering) - { - _suppressInputFormatterBuffering = suppressInputFormatterBuffering; - SupportedEncodings.Add(UTF8EncodingWithoutBOM); SupportedEncodings.Add(UTF16EncodingLittleEndian); @@ -56,6 +47,29 @@ namespace Microsoft.AspNetCore.Mvc.Formatters WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); } + /// + /// Initializes a new instance of . + /// + /// Flag to buffer entire request body before deserializing it. + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public XmlSerializerInputFormatter(bool suppressInputFormatterBuffering) + : this() + { + _suppressInputFormatterBuffering = suppressInputFormatterBuffering; + } + + /// + /// Initializes a new instance of . + /// + /// The . + public XmlSerializerInputFormatter(MvcOptions options) +#pragma warning disable CS0618 + : this() +#pragma warning restore CS0618 + { + _options = options; + } + /// /// Gets the list of to /// provide the wrapping type for de-serialization. @@ -107,7 +121,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters var request = context.HttpContext.Request; - if (!request.Body.CanSeek && !_suppressInputFormatterBuffering) + var suppressInputFormatterBuffering = _options?.SuppressInputFormatterBuffering ?? _suppressInputFormatterBuffering; + + if (!request.Body.CanSeek && !suppressInputFormatterBuffering) { // XmlSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs index b50afa25cf..79875934f4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs @@ -243,10 +243,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { return new TheoryData() { - { new XmlSerializerInputFormatter(), InputFormatterExceptionModelStatePolicy.AllExceptions }, - { new XmlSerializerInputFormatter(), InputFormatterExceptionModelStatePolicy.MalformedInputExceptions }, - { new XmlDataContractSerializerInputFormatter(), InputFormatterExceptionModelStatePolicy.AllExceptions }, - { new XmlDataContractSerializerInputFormatter(), InputFormatterExceptionModelStatePolicy.MalformedInputExceptions }, + { new XmlSerializerInputFormatter(new MvcOptions()), InputFormatterExceptionModelStatePolicy.AllExceptions }, + { new XmlSerializerInputFormatter(new MvcOptions()), InputFormatterExceptionModelStatePolicy.MalformedInputExceptions }, + { new XmlDataContractSerializerInputFormatter(new MvcOptions()), InputFormatterExceptionModelStatePolicy.AllExceptions }, + { new XmlDataContractSerializerInputFormatter(new MvcOptions()), InputFormatterExceptionModelStatePolicy.MalformedInputExceptions }, }; } } @@ -821,6 +821,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders private bool _throwNonInputFormatterException; public TestableXmlSerializerInputFormatter(bool throwNonInputFormatterException) + : base(new MvcOptions()) { _throwNonInputFormatterException = throwNonInputFormatterException; } @@ -842,6 +843,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders private bool _throwNonInputFormatterException; public TestableXmlDataContractSerializerInputFormatter(bool throwNonInputFormatterException) + : base(new MvcOptions()) { _throwNonInputFormatterException = throwNonInputFormatterException; } @@ -885,6 +887,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders private bool _throwNonInputFormatterException; public DerivedXmlSerializerInputFormatter(bool throwNonInputFormatterException) + : base(new MvcOptions()) { _throwNonInputFormatterException = throwNonInputFormatterException; } @@ -906,6 +909,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders private bool _throwNonInputFormatterException; public DerivedXmlDataContractSerializerInputFormatter(bool throwNonInputFormatterException) + : base(new MvcOptions()) { _throwNonInputFormatterException = throwNonInputFormatterException; } diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs index 4833c0dea9..5edf2998e6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs @@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml public void CanRead_ReturnsTrueForAnySupportedContentType(string requestContentType, bool expectedCanRead) { // Arrange - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes("content"); var modelState = new ModelStateDictionary(); @@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml public void HasProperSuppportedMediaTypes() { // Arrange & Act - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); // Assert Assert.Contains("application/xml", formatter.SupportedMediaTypes @@ -127,7 +127,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml public void HasProperSuppportedEncodings() { // Arrange & Act - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); // Assert Assert.Contains(formatter.SupportedEncodings, i => i.WebName == "utf-8"); @@ -142,10 +142,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var expectedString = "TestString"; var input = "" + - "" + expectedInt + "" + - "" + expectedString + ""; + "" + expectedInt + "" + + "" + expectedString + ""; +#pragma warning disable CS0618 var formatter = new XmlDataContractSerializerInputFormatter(); +#pragma warning restore CS0618 + var contentBytes = Encoding.UTF8.GetBytes(input); var httpContext = new DefaultHttpContext(); httpContext.Features.Set(new TestResponseFeature()); @@ -186,10 +189,92 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var expectedString = "TestString"; var input = "" + - "" + expectedInt + "" + - "" + expectedString + ""; + "" + expectedInt + "" + + "" + expectedString + ""; +#pragma warning disable CS0618 var formatter = new XmlDataContractSerializerInputFormatter(suppressInputFormatterBuffering: true); +#pragma warning restore CS0618 + + var contentBytes = Encoding.UTF8.GetBytes(input); + var httpContext = new DefaultHttpContext(); + httpContext.Features.Set(new TestResponseFeature()); + httpContext.Request.Body = new NonSeekableReadStream(contentBytes); + httpContext.Request.ContentType = "application/xml"; + var context = GetInputFormatterContext(httpContext, typeof(TestLevelOne)); + + // Act + var result = await formatter.ReadAsync(context); + + // Assert + Assert.NotNull(result); + Assert.False(result.HasError); + var model = Assert.IsType(result.Model); + + Assert.Equal(expectedInt, model.SampleInt); + Assert.Equal(expectedString, model.sampleString); + + // Reading again should fail as buffering request body is disabled + await Assert.ThrowsAsync(() => formatter.ReadAsync(context)); + } + + [Fact] + public async Task BuffersRequestBody_ByDefaultUsingMvcOptions() + { + // Arrange + var expectedInt = 10; + var expectedString = "TestString"; + + var input = "" + + "" + expectedInt + "" + + "" + expectedString + ""; + + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); + var contentBytes = Encoding.UTF8.GetBytes(input); + var httpContext = new DefaultHttpContext(); + httpContext.Features.Set(new TestResponseFeature()); + httpContext.Request.Body = new NonSeekableReadStream(contentBytes); + httpContext.Request.ContentType = "application/json"; + var context = GetInputFormatterContext(httpContext, typeof(TestLevelOne)); + + // Act + var result = await formatter.ReadAsync(context); + + // Assert + Assert.NotNull(result); + Assert.False(result.HasError); + var model = Assert.IsType(result.Model); + + Assert.Equal(expectedInt, model.SampleInt); + Assert.Equal(expectedString, model.sampleString); + + Assert.True(httpContext.Request.Body.CanSeek); + httpContext.Request.Body.Seek(0L, SeekOrigin.Begin); + + result = await formatter.ReadAsync(context); + + // Assert + Assert.NotNull(result); + Assert.False(result.HasError); + model = Assert.IsType(result.Model); + + Assert.Equal(expectedInt, model.SampleInt); + Assert.Equal(expectedString, model.sampleString); + } + + [Fact] + public async Task SuppressInputFormatterBufferingSetToTrue_DoesNotBufferRequestBody_UsingMvcOptions() + { + // Arrange + var expectedInt = 10; + var expectedString = "TestString"; + + var input = "" + + "" + expectedInt + "" + + "" + expectedString + ""; + + var formatter = new XmlDataContractSerializerInputFormatter( + new MvcOptions() { SuppressInputFormatterBuffering = true }); var contentBytes = Encoding.UTF8.GetBytes(input); var httpContext = new DefaultHttpContext(); httpContext.Features.Set(new TestResponseFeature()); @@ -220,10 +305,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var expectedString = "TestString"; var input = "" + - "" + expectedInt + "" + - "" + expectedString + ""; + "" + expectedInt + "" + + "" + expectedString + ""; - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelOne)); @@ -239,6 +324,43 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml Assert.Equal(expectedString, model.sampleString); } + [Fact] + public async Task SuppressInputFormatterBufferingSetToTrue_UsingMutatedOptions() + { + // Arrange + var expectedInt = 10; + var expectedString = "TestString"; + + var input = "" + + "" + expectedInt + "" + + "" + expectedString + ""; + + var mvcOptions = new MvcOptions() { SuppressInputFormatterBuffering = false }; + var formatter = new XmlDataContractSerializerInputFormatter(mvcOptions); + var contentBytes = Encoding.UTF8.GetBytes(input); + var httpContext = new DefaultHttpContext(); + httpContext.Features.Set(new TestResponseFeature()); + httpContext.Request.Body = new NonSeekableReadStream(contentBytes); + httpContext.Request.ContentType = "application/xml"; + var context = GetInputFormatterContext(httpContext, typeof(TestLevelOne)); + + // Act + // Mutate options after passing into the constructor to make sure that the value type is not store in the constructor + mvcOptions.SuppressInputFormatterBuffering = true; + var result = await formatter.ReadAsync(context); + + // Assert + Assert.NotNull(result); + Assert.False(result.HasError); + var model = Assert.IsType(result.Model); + + Assert.Equal(expectedInt, model.SampleInt); + Assert.Equal(expectedString, model.sampleString); + + // Reading again should fail as buffering request body is disabled + await Assert.ThrowsAsync(() => formatter.ReadAsync(context)); + } + [Fact] public async Task ReadAsync_ReadsComplexTypes() { @@ -248,11 +370,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var expectedLevelTwoString = "102"; var input = "" + - "" + expectedLevelTwoString + "" + - "" + expectedInt + "" + - "" + expectedString + ""; + "" + expectedLevelTwoString + "" + + "" + expectedInt + "" + + "" + expectedString + ""; - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); @@ -277,7 +399,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var input = "" + "" + expectedInt + ""; - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); formatter.MaxDepth = 10; var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); @@ -298,10 +420,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { // Arrange var input = "" + - "test" + - "10" + - "test"; - var formatter = new XmlDataContractSerializerInputFormatter(); + "test" + + "10test"; + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); formatter.MaxDepth = 1; var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); @@ -315,10 +436,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { // Arrange var input = "" + - "test" + - "10" + - "test"; - var formatter = new XmlDataContractSerializerInputFormatter(); + "test10" + + "test"; + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); formatter.XmlDictionaryReaderQuotas.MaxStringContentLength = 2; var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); @@ -331,7 +451,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml public void SetMaxDepth_ThrowsWhenMaxDepthIsBelowOne() { // Arrange - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); // Act & Assert Assert.Throws(() => formatter.MaxDepth = 0); @@ -343,7 +463,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml // Arrange var input = "" + "10"; - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); @@ -372,7 +492,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml Buffer.BlockCopy(inp, 0, contentBytes, inpStart.Length, inp.Length); Buffer.BlockCopy(inpEnd, 0, contentBytes, inpStart.Length + inp.Length, inpEnd.Length); - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); // Act @@ -390,7 +510,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var inputBytes = Encoding.UTF8.GetBytes("" + "1000"); - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); var modelState = new ModelStateDictionary(); var httpContext = GetHttpContext(inputBytes, contentType: "application/xml; charset=utf-16"); @@ -427,7 +547,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml Buffer.BlockCopy(bom, 0, contentBytes, inputStart.Length, bom.Length); Buffer.BlockCopy(inputEnd, 0, contentBytes, inputStart.Length + bom.Length, inputEnd.Length); - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); // Act @@ -453,7 +573,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml "" + expectedInt + "" + "" + expectedString + ""; - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.Unicode.GetBytes(input); var modelState = new ModelStateDictionary(); @@ -491,7 +611,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml "<{0} xmlns=\"{1}\">1", SubstituteRootName, SubstituteRootNamespace); - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); @@ -519,7 +639,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml RootName = dictionary.Add(SubstituteRootName), RootNamespace = dictionary.Add(SubstituteRootNamespace) }; - var formatter = new XmlDataContractSerializerInputFormatter + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()) { SerializerSettings = settings }; @@ -548,7 +668,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml + "Some text", KnownTypeName, InstanceNamespace); - var formatter = new XmlDataContractSerializerInputFormatter(); + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); @@ -576,7 +696,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { KnownTypes = new[] { typeof(SomeDummyClass) } }; - var formatter = new XmlDataContractSerializerInputFormatter + var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()) { SerializerSettings = settings }; @@ -632,6 +752,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { public int createSerializerCalledCount = 0; + public TestXmlDataContractSerializerInputFormatter() + : base(new MvcOptions()) + { + } + protected override DataContractSerializer CreateSerializer(Type type) { createSerializerCalledCount++; diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs index e8b80b3582..84360e3c94 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs @@ -49,11 +49,108 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var expectedDateTime = XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc); var input = "" + - "" + expectedInt + "" + - "" + expectedString + "" + - "" + expectedDateTime + ""; + "" + expectedInt + "" + + "" + expectedString + "" + + "" + expectedDateTime + ""; +#pragma warning disable CS0618 var formatter = new XmlSerializerInputFormatter(); +#pragma warning restore CS0618 + + var contentBytes = Encoding.UTF8.GetBytes(input); + var httpContext = new DefaultHttpContext(); + httpContext.Features.Set(new TestResponseFeature()); + httpContext.Request.Body = new NonSeekableReadStream(contentBytes); + httpContext.Request.ContentType = "application/json"; + var context = GetInputFormatterContext(httpContext, typeof(TestLevelOne)); + + // Act + var result = await formatter.ReadAsync(context); + + // Assert + Assert.NotNull(result); + Assert.False(result.HasError); + var model = Assert.IsType(result.Model); + + Assert.Equal(expectedInt, model.SampleInt); + Assert.Equal(expectedString, model.sampleString); + Assert.Equal( + XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), + model.SampleDate); + + Assert.True(httpContext.Request.Body.CanSeek); + httpContext.Request.Body.Seek(0L, SeekOrigin.Begin); + + result = await formatter.ReadAsync(context); + + // Assert + Assert.NotNull(result); + Assert.False(result.HasError); + model = Assert.IsType(result.Model); + + Assert.Equal(expectedInt, model.SampleInt); + Assert.Equal(expectedString, model.sampleString); + Assert.Equal( + XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), + model.SampleDate); + } + + [Fact] + public async Task SuppressInputFormatterBufferingSetToTrue_DoesNotBufferRequestBody_ObsoleteParameter() + { + // Arrange + var expectedInt = 10; + var expectedString = "TestString"; + var expectedDateTime = XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc); + + var input = "" + + "" + expectedInt + "" + + "" + expectedString + "" + + "" + expectedDateTime + ""; + +#pragma warning disable CS0618 + var formatter = new XmlSerializerInputFormatter(suppressInputFormatterBuffering: true); +#pragma warning restore CS0618 + + var contentBytes = Encoding.UTF8.GetBytes(input); + var httpContext = new DefaultHttpContext(); + httpContext.Features.Set(new TestResponseFeature()); + httpContext.Request.Body = new NonSeekableReadStream(contentBytes); + httpContext.Request.ContentType = "application/xml"; + var context = GetInputFormatterContext(httpContext, typeof(TestLevelOne)); + + // Act + var result = await formatter.ReadAsync(context); + + // Assert + Assert.NotNull(result); + Assert.False(result.HasError); + var model = Assert.IsType(result.Model); + + Assert.Equal(expectedInt, model.SampleInt); + Assert.Equal(expectedString, model.sampleString); + Assert.Equal( + XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), + model.SampleDate); + + // Reading again should fail as buffering request body is disabled + await Assert.ThrowsAsync(() => formatter.ReadAsync(context)); + } + + [Fact] + public async Task BuffersRequestBody_ByDefaultUsingMvcOptions() + { + // Arrange + var expectedInt = 10; + var expectedString = "TestString"; + var expectedDateTime = XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc); + + var input = "" + + "" + expectedInt + "" + + "" + expectedString + "" + + "" + expectedDateTime + ""; + + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes(input); var httpContext = new DefaultHttpContext(); httpContext.Features.Set(new TestResponseFeature()); @@ -101,11 +198,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var expectedDateTime = XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc); var input = "" + - "" + expectedInt + "" + - "" + expectedString + "" + - "" + expectedDateTime + ""; + "" + expectedInt + "" + + "" + expectedString + "" + + "" + expectedDateTime + ""; - var formatter = new XmlSerializerInputFormatter(suppressInputFormatterBuffering: true); + var formatter = new XmlSerializerInputFormatter(new MvcOptions() { SuppressInputFormatterBuffering = true }); var contentBytes = Encoding.UTF8.GetBytes(input); var httpContext = new DefaultHttpContext(); httpContext.Features.Set(new TestResponseFeature()); @@ -131,6 +228,50 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml await Assert.ThrowsAsync(() => formatter.ReadAsync(context)); } + [Fact] + public async Task SuppressInputFormatterBufferingSetToTrue_UsingMutatedOptions() + { + // Arrange + var expectedInt = 10; + var expectedString = "TestString"; + var expectedDateTime = XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc); + + var input = "" + + "" + expectedInt + "" + + "" + expectedString + "" + + "" + expectedDateTime + ""; + + var mvcOptions = new MvcOptions(); + mvcOptions.SuppressInputFormatterBuffering = false; + var formatter = new XmlSerializerInputFormatter(mvcOptions); + + var contentBytes = Encoding.UTF8.GetBytes(input); + var httpContext = new DefaultHttpContext(); + httpContext.Features.Set(new TestResponseFeature()); + httpContext.Request.Body = new NonSeekableReadStream(contentBytes); + httpContext.Request.ContentType = "application/xml"; + var context = GetInputFormatterContext(httpContext, typeof(TestLevelOne)); + + // Act + // Mutate options after passing into the constructor to make sure that the value type is not store in the constructor + mvcOptions.SuppressInputFormatterBuffering = true; + var result = await formatter.ReadAsync(context); + + // Assert + Assert.NotNull(result); + Assert.False(result.HasError); + var model = Assert.IsType(result.Model); + + Assert.Equal(expectedInt, model.SampleInt); + Assert.Equal(expectedString, model.sampleString); + Assert.Equal( + XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), + model.SampleDate); + + // Reading again should fail as buffering request body is disabled + await Assert.ThrowsAsync(() => formatter.ReadAsync(context)); + } + [Theory] [InlineData("application/xml", true)] [InlineData("application/*", false)] @@ -150,7 +291,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml public void CanRead_ReturnsTrueForAnySupportedContentType(string requestContentType, bool expectedCanRead) { // Arrange - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes("content"); var modelState = new ModelStateDictionary(); @@ -178,7 +319,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml public void CanRead_ReturnsFalse_ForAnyUnsupportedModelType(Type modelType, bool expectedCanRead) { // Arrange - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes("content"); var context = GetInputFormatterContext(contentBytes, modelType); @@ -212,7 +353,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml public void HasProperSuppportedMediaTypes() { // Arrange & Act - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); // Assert Assert.Contains("application/xml", formatter.SupportedMediaTypes @@ -225,7 +366,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml public void HasProperSuppportedEncodings() { // Arrange & Act - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); // Assert Assert.Contains(formatter.SupportedEncodings, i => i.WebName == "utf-8"); @@ -245,7 +386,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml "" + expectedString + "" + "" + expectedDateTime + ""; - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelOne)); @@ -279,7 +420,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml "" + expectedString + "" + "" + expectedDateTime + ""; - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); @@ -307,7 +448,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var input = "" + "" + expectedInt + ""; - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); formatter.MaxDepth = 10; var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); @@ -334,7 +475,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml "test" + "" + XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc) + ""; - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); formatter.MaxDepth = 1; var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); @@ -355,7 +496,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml "test" + "" + XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc) + ""; - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); formatter.XmlDictionaryReaderQuotas.MaxStringContentLength = 10; var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); @@ -368,7 +509,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml public void SetMaxDepth_ThrowsWhenMaxDepthIsBelowOne() { // Arrange - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); // Act & Assert Assert.Throws(() => formatter.MaxDepth = 0); @@ -380,7 +521,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml // Arrange var input = "" + "10"; - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.UTF8.GetBytes(input); var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); @@ -412,7 +553,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml Buffer.BlockCopy(inp, 0, contentBytes, inpStart.Length, inp.Length); Buffer.BlockCopy(inpEnd, 0, contentBytes, inpStart.Length + inp.Length, inpEnd.Length); - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); // Act and Assert @@ -431,7 +572,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var inputBytes = Encoding.UTF8.GetBytes("" + "1000"); - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); var modelState = new ModelStateDictionary(); var httpContext = GetHttpContext(inputBytes, contentType: "application/xml; charset=utf-16"); @@ -467,7 +608,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml Buffer.BlockCopy(bom, 0, contentBytes, inputStart.Length, bom.Length); Buffer.BlockCopy(inputEnd, 0, contentBytes, inputStart.Length + bom.Length, inputEnd.Length); - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); // Act @@ -495,7 +636,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml "" + expectedString + "" + "" + expectedDateTime + ""; - var formatter = new XmlSerializerInputFormatter(); + var formatter = new XmlSerializerInputFormatter(new MvcOptions()); var contentBytes = Encoding.Unicode.GetBytes(input); var modelState = new ModelStateDictionary(); @@ -560,6 +701,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { public int createSerializerCalledCount = 0; + public TestXmlSerializerInputFormatter() + : base(new MvcOptions()) + { + } + protected override XmlSerializer CreateSerializer(Type type) { createSerializerCalledCount++; diff --git a/test/WebSites/XmlFormattersWebSite/Startup.cs b/test/WebSites/XmlFormattersWebSite/Startup.cs index 7bdfc7871d..5414e57643 100644 --- a/test/WebSites/XmlFormattersWebSite/Startup.cs +++ b/test/WebSites/XmlFormattersWebSite/Startup.cs @@ -30,7 +30,7 @@ namespace XmlFormattersWebSite // request information (Ex: Accept header). // So here we instead clear out the default supported media types and create new // ones which are distinguishable between formatters. - var xmlSerializerInputFormatter = new XmlSerializerInputFormatter(); + var xmlSerializerInputFormatter = new XmlSerializerInputFormatter(new MvcOptions()); xmlSerializerInputFormatter.SupportedMediaTypes.Clear(); xmlSerializerInputFormatter.SupportedMediaTypes.Add( new MediaTypeHeaderValue("application/xml-xmlser")); @@ -44,7 +44,7 @@ namespace XmlFormattersWebSite xmlSerializerOutputFormatter.SupportedMediaTypes.Add( new MediaTypeHeaderValue("text/xml-xmlser")); - var dcsInputFormatter = new XmlDataContractSerializerInputFormatter(); + var dcsInputFormatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); dcsInputFormatter.SupportedMediaTypes.Clear(); dcsInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml-dcs")); dcsInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml-dcs"));