diff --git a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerInputFormatter.cs index e725bed1c6..dda0e1a700 100644 --- a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerInputFormatter.cs @@ -20,6 +20,7 @@ namespace Microsoft.AspNet.Mvc.Xml /// public class XmlDataContractSerializerInputFormatter : IInputFormatter { + private DataContractSerializerSettings _serializerSettings; private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas(); /// @@ -33,6 +34,7 @@ namespace Microsoft.AspNet.Mvc.Xml SupportedMediaTypes = new List(); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml")); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml")); + _serializerSettings = new DataContractSerializerSettings(); } /// @@ -73,6 +75,24 @@ namespace Microsoft.AspNet.Mvc.Xml .Any(supportedMediaType => supportedMediaType.IsSubsetOf(requestContentType)); } + /// + /// Gets or sets the used to configure the + /// . + /// + public DataContractSerializerSettings SerializerSettings + { + get { return _serializerSettings; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _serializerSettings = value; + } + } + /// /// Reads the input XML. /// @@ -106,7 +126,7 @@ namespace Microsoft.AspNet.Mvc.Xml /// The used during deserialization. protected virtual XmlObjectSerializer CreateDataContractSerializer(Type type) { - return new DataContractSerializer(SerializableErrorWrapper.CreateSerializableType(type)); + return new DataContractSerializer(SerializableErrorWrapper.CreateSerializableType(type), _serializerSettings); } private object GetDefaultValueForType(Type modelType) diff --git a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerOutputFormatter.cs index e132106133..a751f202f9 100644 --- a/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Xml/Formatters/XmlDataContractSerializerOutputFormatter.cs @@ -16,6 +16,8 @@ namespace Microsoft.AspNet.Mvc.Xml /// public class XmlDataContractSerializerOutputFormatter : OutputFormatter { + private DataContractSerializerSettings _serializerSettings; + /// /// Initializes a new instance of /// with default XmlWriterSettings @@ -38,6 +40,7 @@ namespace Microsoft.AspNet.Mvc.Xml SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml")); WriterSettings = writerSettings; + _serializerSettings = new DataContractSerializerSettings(); } /// @@ -45,6 +48,24 @@ namespace Microsoft.AspNet.Mvc.Xml /// public XmlWriterSettings WriterSettings { get; } + /// + /// Gets or sets the used to configure the + /// . + /// + public DataContractSerializerSettings SerializerSettings + { + get { return _serializerSettings; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _serializerSettings = value; + } + } + /// /// Gets the type of the object to be serialized. /// @@ -85,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Xml FormattingUtilities.XsdDataContractExporter.GetRootElementName(type); #endif // If the serializer does not support this type it will throw an exception. - return new DataContractSerializer(type); + return new DataContractSerializer(type, _serializerSettings); } catch (Exception) { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerInputFormatterTest.cs index d67cf74186..95dd8baf21 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerInputFormatterTest.cs @@ -27,6 +27,13 @@ namespace Microsoft.AspNet.Mvc public int SampleInt { get; set; } } + [DataContract(Name = "SomeDummyClass", Namespace = "")] + public class SomeDummyClass : DummyClass + { + [DataMember] + public string SampleString { get; set; } + } + [DataContract(Name = "TestLevelOne", Namespace = "")] public class TestLevelOne { @@ -359,6 +366,118 @@ namespace Microsoft.AspNet.Mvc Assert.Equal("Test Error 3", serializableError["key2"]); } + [Fact] + public async Task XmlDataContractSerializerFormatterThrowsWhenNotConfiguredWithRootName() + { + // Arrange + var SubstituteRootName = "SomeOtherClass"; + var SubstituteRootNamespace = "http://tempuri.org"; + + var input = string.Format( + "<{0} xmlns=\"{1}\">1", + SubstituteRootName, + SubstituteRootNamespace); + var formatter = new XmlDataContractSerializerInputFormatter(); + var contentBytes = Encoding.UTF8.GetBytes(input); + var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); + + // Act & Assert + await Assert.ThrowsAsync(typeof(SerializationException), async () => await formatter.ReadAsync(context)); + } + + [Fact] + public async Task XmlDataContractSerializerFormatterReadsWhenConfiguredWithRootName() + { + // Arrange + var expectedInt = 10; + var SubstituteRootName = "SomeOtherClass"; + var SubstituteRootNamespace = "http://tempuri.org"; + + var input = string.Format( + "<{0} xmlns=\"{1}\">{2}", + SubstituteRootName, + SubstituteRootNamespace, + expectedInt); + + var dictionary = new XmlDictionary(); + var settings = new DataContractSerializerSettings + { + RootName = dictionary.Add(SubstituteRootName), + RootNamespace = dictionary.Add(SubstituteRootNamespace) + }; + var formatter = new XmlDataContractSerializerInputFormatter + { + SerializerSettings = settings + }; + var contentBytes = Encoding.UTF8.GetBytes(input); + var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); + + // Act + var model = await formatter.ReadAsync(context); + + // Assert + Assert.NotNull(model); + var dummyModel = Assert.IsType(model); + Assert.Equal(expectedInt, dummyModel.SampleInt); + } + + [Fact] + public async Task XmlDataContractSerializerFormatterThrowsWhenNotConfiguredWithKnownTypes() + { + // Arrange + var KnownTypeName = "SomeDummyClass"; + var InstanceNamespace = "http://www.w3.org/2001/XMLSchema-instance"; + + var input = string.Format( + "1" + + "Some text", + KnownTypeName, + InstanceNamespace); + var formatter = new XmlDataContractSerializerInputFormatter(); + var contentBytes = Encoding.UTF8.GetBytes(input); + var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); + + // Act & Assert + await Assert.ThrowsAsync(typeof(SerializationException), async () => await formatter.ReadAsync(context)); + } + + [Fact] + public async Task XmlDataContractSerializerFormatterReadsWhenConfiguredWithKnownTypes() + { + // Arrange + var expectedInt = 10; + var expectedString = "TestString"; + var KnownTypeName = "SomeDummyClass"; + var InstanceNamespace = "http://www.w3.org/2001/XMLSchema-instance"; + + var input = string.Format( + "{2}" + + "{3}", + KnownTypeName, + InstanceNamespace, + expectedInt, + expectedString); + var settings = new DataContractSerializerSettings + { + KnownTypes = new[] { typeof(SomeDummyClass) } + }; + var formatter = new XmlDataContractSerializerInputFormatter + { + SerializerSettings = settings + }; + var contentBytes = Encoding.UTF8.GetBytes(input); + var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); + + // Act + var model = await formatter.ReadAsync(context); + + // Assert + Assert.NotNull(model); + var dummyModel = Assert.IsType(model); + Assert.Equal(expectedInt, dummyModel.SampleInt); + Assert.Equal(expectedString, dummyModel.SampleString); + } + private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType) { var actionContext = GetActionContext(contentBytes); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs index ffdeb14418..cc3d1e9e8e 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs @@ -7,10 +7,11 @@ using System.IO; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; +using System.Xml; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Core.Collections; -using Microsoft.Net.Http.Headers; using Microsoft.AspNet.Mvc.Xml; +using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -25,6 +26,13 @@ namespace Microsoft.AspNet.Mvc.Core public int SampleInt { get; set; } } + [DataContract(Name = "SomeDummyClass", Namespace = "")] + public class SomeDummyClass : DummyClass + { + [DataMember] + public string SampleString { get; set; } + } + [DataContract(Name = "TestLevelOne", Namespace = "")] public class TestLevelOne { @@ -43,6 +51,24 @@ namespace Microsoft.AspNet.Mvc.Core public TestLevelOne TestOne { get; set; } } + [DataContract(Name = "Child", Namespace = "")] + public class Child + { + [DataMember] + public int Id { get; set; } + [DataMember] + public Parent Parent { get; set; } + } + + [DataContract(Name = "Parent", Namespace = "")] + public class Parent + { + [DataMember] + public string Name { get; set; } + [DataMember] + public List Children { get; set; } + } + public static IEnumerable BasicTypeValues { get @@ -194,9 +220,10 @@ namespace Microsoft.AspNet.Mvc.Core Assert.NotNull(outputFormatterContext.ActionContext.HttpContext.Response.Body); outputFormatterContext.ActionContext.HttpContext.Response.Body.Position = 0; Assert.Equal("" + - "" + - "10", - new StreamReader(outputFormatterContext.ActionContext.HttpContext.Response.Body, Encoding.UTF8).ReadToEnd()); + "" + + "10", + new StreamReader(outputFormatterContext.ActionContext.HttpContext.Response.Body, + Encoding.UTF8).ReadToEnd()); } [Fact] @@ -320,7 +347,8 @@ namespace Microsoft.AspNet.Mvc.Core [Theory] [MemberData(nameof(TypesForGetSupportedContentTypes))] - public void XmlDataContractSerializer_GetSupportedContentTypes_Returns_SupportedTypes(Type declaredType, Type runtimeType, object expectedOutput) + public void XmlDataContractSerializer_GetSupportedContentTypes_Returns_SupportedTypes(Type declaredType, + Type runtimeType, object expectedOutput) { // Arrange var formatter = new XmlDataContractSerializerOutputFormatter(); @@ -340,6 +368,164 @@ namespace Microsoft.AspNet.Mvc.Core } } + [Fact] + public async Task XmlDataContractSerializerOutputFormatterThrowsWhenNotConfiguredWithKnownTypes() + { + // Arrange + var sampleInput = new SomeDummyClass { SampleInt = 1, SampleString = "TestString" }; + var formatter = new XmlDataContractSerializerOutputFormatter(); + var outputFormatterContext = GetOutputFormatterContext(sampleInput, typeof(DummyClass)); + + // Act & Assert + await Assert.ThrowsAsync(typeof(SerializationException), + async () => await formatter.WriteAsync(outputFormatterContext)); + } + + [Fact] + public async Task XmlDataContractSerializerOutputFormatterThrowsWhenNotConfiguredWithPreserveReferences() + { + // Arrange + var child = new Child { Id = 1 }; + var parent = new Parent { Name = "Parent", Children = new List { child } }; + child.Parent = parent; + + var formatter = new XmlDataContractSerializerOutputFormatter(); + var outputFormatterContext = GetOutputFormatterContext(parent, parent.GetType()); + + // Act & Assert + await Assert.ThrowsAsync(typeof(SerializationException), + async () => await formatter.WriteAsync(outputFormatterContext)); + } + + [Fact] + public async Task XmlDataContractSerializerFormatterWritesWhenConfiguredWithRootName() + { + // Arrange + var sampleInt = 10; + var SubstituteRootName = "SomeOtherClass"; + var SubstituteRootNamespace = "http://tempuri.org"; + var InstanceNamespace = "http://www.w3.org/2001/XMLSchema-instance"; + + var expectedOutput = string.Format( + "<{0} xmlns:i=\"{2}\" xmlns=\"{1}\">{3}", + SubstituteRootName, + SubstituteRootNamespace, + InstanceNamespace, + sampleInt); + + var sampleInput = new DummyClass { SampleInt = sampleInt }; + + var dictionary = new XmlDictionary(); + var settings = new DataContractSerializerSettings + { + RootName = dictionary.Add(SubstituteRootName), + RootNamespace = dictionary.Add(SubstituteRootNamespace) + }; + var formatter = new XmlDataContractSerializerOutputFormatter + { + SerializerSettings = settings + }; + var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); + + // Act + await formatter.WriteAsync(outputFormatterContext); + + // Assert + Assert.NotNull(outputFormatterContext.ActionContext.HttpContext.Response.Body); + outputFormatterContext.ActionContext.HttpContext.Response.Body.Position = 0; + var actualOutput = new StreamReader( + outputFormatterContext.ActionContext.HttpContext.Response.Body, Encoding.UTF8).ReadToEnd(); + Assert.Equal(expectedOutput, actualOutput); + } + + [Fact] + public async Task XmlDataContractSerializerFormatterWritesWhenConfiguredWithKnownTypes() + { + // Arrange + var sampleInt = 10; + var sampleString = "TestString"; + var KnownTypeName = "SomeDummyClass"; + var InstanceNamespace = "http://www.w3.org/2001/XMLSchema-instance"; + + var expectedOutput = string.Format( + "{2}" + + "{3}", + KnownTypeName, + InstanceNamespace, + sampleInt, + sampleString); + + var sampleInput = new SomeDummyClass + { + SampleInt = sampleInt, + SampleString = sampleString + }; + + var settings = new DataContractSerializerSettings + { + KnownTypes = new[] { typeof(SomeDummyClass) } + }; + var formatter = new XmlDataContractSerializerOutputFormatter + { + SerializerSettings = settings + }; + var outputFormatterContext = GetOutputFormatterContext(sampleInput, typeof(DummyClass)); + + // Act + await formatter.WriteAsync(outputFormatterContext); + + // Assert + Assert.NotNull(outputFormatterContext.ActionContext.HttpContext.Response.Body); + outputFormatterContext.ActionContext.HttpContext.Response.Body.Position = 0; + var actualOutput = new StreamReader( + outputFormatterContext.ActionContext.HttpContext.Response.Body, Encoding.UTF8).ReadToEnd(); + Assert.Equal(expectedOutput, actualOutput); + } + + [Fact] + public async Task XmlDataContractSerializerFormatterWritesWhenConfiguredWithPreserveReferences() + { + // Arrange + var sampleId = 1; + var sampleName = "Parent"; + var InstanceNamespace = "http://www.w3.org/2001/XMLSchema-instance"; + var SerializationNamespace = "http://schemas.microsoft.com/2003/10/Serialization/"; + + var expectedOutput = string.Format( + "" + + "" + + "{2}" + + "{3}", + InstanceNamespace, + SerializationNamespace, + sampleId, + sampleName); + + var child = new Child { Id = sampleId }; + var parent = new Parent { Name = sampleName, Children = new List { child } }; + child.Parent = parent; + + var settings = new DataContractSerializerSettings + { + PreserveObjectReferences = true + }; + var formatter = new XmlDataContractSerializerOutputFormatter + { + SerializerSettings = settings + }; + var outputFormatterContext = GetOutputFormatterContext(parent, parent.GetType()); + + // Act + await formatter.WriteAsync(outputFormatterContext); + + // Assert + Assert.NotNull(outputFormatterContext.ActionContext.HttpContext.Response.Body); + outputFormatterContext.ActionContext.HttpContext.Response.Body.Position = 0; + var actualOutput = new StreamReader( + outputFormatterContext.ActionContext.HttpContext.Response.Body, Encoding.UTF8).ReadToEnd(); + Assert.Equal(expectedOutput, actualOutput); + } + private OutputFormatterContext GetOutputFormatterContext(object outputValue, Type outputType, string contentType = "application/xml; charset=utf-8") {