From b9f158600820f3c34f32a4ee4eaab0bdec61e7cc Mon Sep 17 00:00:00 2001 From: sornaks Date: Tue, 19 Aug 2014 11:19:30 -0700 Subject: [PATCH] Changes to introduce CanWriteResult for Xml OutputFormatters. --- .../Formatters/FormattingUtilities.cs | 9 ++--- .../Formatters/OutputFormatter.cs | 20 ++++++++++- ...mlDataContractSerializerOutputFormatter.cs | 22 +++++++++++-- .../Formatters/XmlOutputFormatter.cs | 22 +++++++++++++ .../XmlSerializerOutputFormatter.cs | 18 ++++++++-- ...aContractSerializerOutputFormatterTests.cs | 14 ++++++++ .../XmlSerializerOutputFormatterTests.cs | 26 +++++++++++++++ .../XmlOutputFormatterTests.cs | 21 ++++++++++++ .../project.json | 4 +-- .../DataContractSerializerController.cs | 33 +++++++++++++++++++ .../FormatterWebSite/Models/Person.cs | 19 +++++++++++ 11 files changed, 195 insertions(+), 13 deletions(-) create mode 100644 test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs create mode 100644 test/WebSites/FormatterWebSite/Models/Person.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs index d8f7cb52c5..5d96c4d1b2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs @@ -2,11 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Text; +using System.Runtime.Serialization; using System.Xml; -using Microsoft.AspNet.Mvc.Core; -using Microsoft.AspNet.Mvc.HeaderValueAbstractions; namespace Microsoft.AspNet.Mvc { @@ -17,6 +14,10 @@ namespace Microsoft.AspNet.Mvc { public static readonly int DefaultMaxDepth = 32; +#if NET45 + public static readonly XsdDataContractExporter XsdDataContractExporter = new XsdDataContractExporter(); +#endif + /// /// Gets the default Reader Quotas for XmlReader. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs index 383117c109..c3b0708dc7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs @@ -67,7 +67,25 @@ namespace Microsoft.AspNet.Mvc encoding = encoding ?? SupportedEncodings.FirstOrDefault(); return encoding; } - + + /// + /// Gets the type of the object to be serialized. + /// + /// The context which contains the object to be serialized. + /// The type of the object to be serialized. + public virtual Type GetObjectType([NotNull] OutputFormatterContext context) + { + if (context.DeclaredType == null) + { + if (context.Object != null) + { + return context.Object.GetType(); + } + } + + return context.DeclaredType; + } + /// public virtual bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs index a2b53da271..02af1d6b6a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs @@ -37,9 +37,25 @@ namespace Microsoft.AspNet.Mvc /// /// The type of object for which the serializer should be created. /// A new instance of - public virtual DataContractSerializer CreateDataContractSerializer([NotNull] Type type) + public override object CreateSerializer([NotNull] Type type) { - return new DataContractSerializer(type); + DataContractSerializer serializer = null; + try + { +#if NET45 + // Verify that type is a valid data contract by forcing the serializer to try to create a data contract + FormattingUtilities.XsdDataContractExporter.GetRootElementName(type); +#endif + // If the serializer does not support this type it will throw an exception. + serializer = new DataContractSerializer(type); + } + catch (Exception) + { + // We do not surface the caught exception because if CanWriteResult returns + // false, then this Formatter is not picked up at all. + } + + return serializer; } /// @@ -51,7 +67,7 @@ namespace Microsoft.AspNet.Mvc tempWriterSettings.Encoding = context.SelectedEncoding; using (var xmlWriter = CreateXmlWriter(response.Body, tempWriterSettings)) { - var dataContractSerializer = CreateDataContractSerializer(context.DeclaredType); + var dataContractSerializer = (DataContractSerializer)CreateSerializer(GetObjectType(context)); dataContractSerializer.WriteObject(xmlWriter, context.Object); } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlOutputFormatter.cs index cf9d1ccb35..f9135b3208 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlOutputFormatter.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.IO; using System.Xml; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; @@ -28,6 +29,27 @@ namespace Microsoft.AspNet.Mvc /// public XmlWriterSettings WriterSettings { get; private set; } + /// + /// Returns a serializer to serialzie the particualr type. + /// + /// The type which needs to be serialized. + /// The serializer object. + public abstract object CreateSerializer(Type type); + + /// + public override bool CanWriteResult([NotNull] OutputFormatterContext context, MediaTypeHeaderValue contentType) + { + if (base.CanWriteResult(context, contentType)) + { + if (CreateSerializer(GetObjectType(context)) != null) + { + return true; + } + } + + return false; + } + /// /// Gets the default XmlWriterSettings. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs index ea1223f658..81a74ea247 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs @@ -37,9 +37,21 @@ namespace Microsoft.AspNet.Mvc /// /// The type of object for which the serializer should be created. /// A new instance of - public virtual XmlSerializer CreateXmlSerializer([NotNull] Type type) + public override object CreateSerializer([NotNull] Type type) { - return new XmlSerializer(type); + XmlSerializer serializer = null; + try + { + // If the serializer does not support this type it will throw an exception. + serializer = new XmlSerializer(type); + } + catch (Exception) + { + // We do not surface the caught exception because if CanWriteResult returns + // false, then this Formatter is not picked up at all. + } + + return serializer; } /// @@ -51,7 +63,7 @@ namespace Microsoft.AspNet.Mvc tempWriterSettings.Encoding = context.SelectedEncoding; using (var xmlWriter = CreateXmlWriter(response.Body, tempWriterSettings)) { - var xmlSerializer = CreateXmlSerializer(context.DeclaredType); + var xmlSerializer = (XmlSerializer)CreateSerializer(GetObjectType(context)); xmlSerializer.Serialize(xmlWriter, context.Object); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs index 2370d00b66..21ed42dd72 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs @@ -2,11 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.HeaderValueAbstractions; using Moq; using Xunit; @@ -180,6 +182,18 @@ namespace Microsoft.AspNet.Mvc.Core Assert.True(outputFormatterContext.ActionContext.HttpContext.Response.Body.CanRead); } + [Fact] + public void XmlDataContractSerializer_CanWriteResult_ReturnsTrue_ForWritableType() + { + // Arrange + var formatter = new XmlDataContractSerializerOutputFormatter( + XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var outputFormatterContext = GetOutputFormatterContext(null, typeof(Dictionary)); + + // Act & Assert + Assert.True(formatter.CanWriteResult(outputFormatterContext, MediaTypeHeaderValue.Parse("application/xml"))); + } + private OutputFormatterContext GetOutputFormatterContext(object outputValue, Type outputType, string contentType = "application/xml; charset=utf-8") { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs index 2ffd2ab87f..75b2a698f3 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs @@ -2,10 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.HeaderValueAbstractions; using Moq; using Xunit; @@ -173,6 +175,30 @@ namespace Microsoft.AspNet.Mvc.Core Assert.True(outputFormatterContext.ActionContext.HttpContext.Response.Body.CanRead); } + [Fact] + public void XmlSerializer_CanWriteResult_ReturnsFalse_ForNonWritableType() + { + // Arrange + var formatter = new XmlSerializerOutputFormatter(); + var outputFormatterContext = GetOutputFormatterContext(outputValue: null, + outputType: typeof(Dictionary)); + + // Act & Assert + Assert.False(formatter.CanWriteResult(outputFormatterContext, MediaTypeHeaderValue.Parse("application/xml"))); + } + + [Fact] + public void XmlDataContractSerializer_CanWriteResult_ReturnsTrue_ForWritableType() + { + // Arrange + var formatter = new XmlSerializerOutputFormatter(); + var outputFormatterContext = GetOutputFormatterContext(outputValue: null, + outputType: typeof(string)); + + // Act & Assert + Assert.True(formatter.CanWriteResult(outputFormatterContext, MediaTypeHeaderValue.Parse("application/xml"))); + } + private OutputFormatterContext GetOutputFormatterContext(object outputValue, Type outputType, string contentType = "application/xml; charset=utf-8") { diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlOutputFormatterTests.cs index 35d74ef7e3..62611648ff 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlOutputFormatterTests.cs @@ -62,5 +62,26 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">10", new StreamReader(response.Body, Encoding.UTF8).ReadToEnd()); } + + [Fact] + public async Task XmlSerializerFailsAndDataContractSerializerIsCalled() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.Handler; + var headers = new Dictionary(); + headers.Add("Accept", new string[] { "application/xml;charset=utf-8" }); + + // Act + var response = await client.SendAsync("POST", + "http://localhost/DataContractSerializer/GetPerson?name=HelloWorld", headers, null, null); + + //Assert + Assert.Equal(200, response.StatusCode); + Assert.Equal("" + + "HelloWorld", + new StreamReader(response.Body, Encoding.UTF8).ReadToEnd()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index 0c2ee64a2e..a56aec347d 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -4,12 +4,12 @@ }, "dependencies": { "ActivatorWebSite": "", + "AntiForgeryWebSite": "", "BasicWebSite": "", "CompositeViewEngine": "", "ConnegWebsite": "", - "FormatterWebSite": "", + "FormatterWebSite": "", "InlineConstraintsWebSite": "", - "AntiForgeryWebSite": "", "Microsoft.AspNet.TestHost": "1.0.0-*", "Microsoft.AspNet.PipelineCore": "1.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "", diff --git a/test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs b/test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs new file mode 100644 index 0000000000..11b1c9ecc4 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Mvc; + +namespace FormatterWebSite +{ + /// + /// Summary description for DataContractSerializerController + /// + public class DataContractSerializerController : Controller + { + public override void OnActionExecuted(ActionExecutedContext context) + { + var result = context.Result as ObjectResult; + if (result != null) + { + result.Formatters.Add(new XmlSerializerOutputFormatter()); + result.Formatters.Add(new XmlDataContractSerializerOutputFormatter()); + } + + base.OnActionExecuted(context); + } + + [HttpPost] + public Person GetPerson(string name) + { + // The XmlSerializer should skip and the + // DataContractSerializer should pick up this output. + return new Person(name); + } + } +} \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Models/Person.cs b/test/WebSites/FormatterWebSite/Models/Person.cs new file mode 100644 index 0000000000..a991a3c88d --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/Person.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.Serialization; + +namespace FormatterWebSite +{ + [DataContract] + public class Person + { + public Person(string name) + { + Name = name; + } + + [DataMember] + public string Name { get; set; } + } +} \ No newline at end of file