Skip to content

Commit

Permalink
Changes to introduce CanWriteResult for Xml OutputFormatters.
Browse files Browse the repository at this point in the history
  • Loading branch information
sornaks committed Aug 20, 2014
1 parent 041d350 commit b9f1586
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -17,6 +14,10 @@ public static class FormattingUtilities
{
public static readonly int DefaultMaxDepth = 32;

#if NET45
public static readonly XsdDataContractExporter XsdDataContractExporter = new XsdDataContractExporter();
#endif

/// <summary>
/// Gets the default Reader Quotas for XmlReader.
/// </summary>
Expand Down
20 changes: 19 additions & 1 deletion src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,25 @@ public virtual Encoding SelectCharacterEncoding(OutputFormatterContext context)
encoding = encoding ?? SupportedEncodings.FirstOrDefault();
return encoding;
}


/// <summary>
/// Gets the type of the object to be serialized.
/// </summary>
/// <param name="context">The context which contains the object to be serialized.</param>
/// <returns>The type of the object to be serialized.</returns>
public virtual Type GetObjectType([NotNull] OutputFormatterContext context)
{
if (context.DeclaredType == null)
{
if (context.Object != null)
{
return context.Object.GetType();
}
}

return context.DeclaredType;
}

/// <inheritdoc />
public virtual bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,25 @@ public XmlDataContractSerializerOutputFormatter([NotNull] XmlWriterSettings writ
/// </summary>
/// <param name="type">The type of object for which the serializer should be created.</param>
/// <returns>A new instance of <see cref="DataContractSerializer"/></returns>
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;
}

/// <inheritdoc />
Expand All @@ -51,7 +67,7 @@ public override Task WriteResponseBodyAsync([NotNull] OutputFormatterContext con
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);
}

Expand Down
22 changes: 22 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/Formatters/XmlOutputFormatter.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -28,6 +29,27 @@ public XmlOutputFormatter([NotNull] XmlWriterSettings xmlWriterSettings)
/// </summary>
public XmlWriterSettings WriterSettings { get; private set; }

/// <summary>
/// Returns a serializer to serialzie the particualr type.
/// </summary>
/// <param name="type">The type which needs to be serialized.</param>
/// <returns>The serializer object.</returns>
public abstract object CreateSerializer(Type type);

/// <inheritdoc />
public override bool CanWriteResult([NotNull] OutputFormatterContext context, MediaTypeHeaderValue contentType)
{
if (base.CanWriteResult(context, contentType))
{
if (CreateSerializer(GetObjectType(context)) != null)
{
return true;
}
}

return false;
}

/// <summary>
/// Gets the default XmlWriterSettings.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,21 @@ public XmlSerializerOutputFormatter([NotNull] XmlWriterSettings writerSettings)
/// </summary>
/// <param name="type">The type of object for which the serializer should be created.</param>
/// <returns>A new instance of <see cref="XmlSerializer"/></returns>
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;
}

/// <inheritdoc />
Expand All @@ -51,7 +63,7 @@ public override Task WriteResponseBodyAsync([NotNull] OutputFormatterContext con
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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -180,6 +182,18 @@ public async Task VerifyBodyIsNotClosedAfterOutputIsWritten()
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<string, 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")
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -173,6 +175,30 @@ public async Task VerifyBodyIsNotClosedAfterOutputIsWritten()
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<string, string>));

// 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")
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,26 @@ public async Task XmlSerializerOutputFormatterIsCalled()
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><SampleInt>10</SampleInt></DummyClass>",
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<string, string[]>();
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("<Person xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" " +
"xmlns=\"http://schemas.datacontract.org/2004/07/FormatterWebSite\">" +
"<Name>HelloWorld</Name></Person>",
new StreamReader(response.Body, Encoding.UTF8).ReadToEnd());
}
}
}
4 changes: 2 additions & 2 deletions test/Microsoft.AspNet.Mvc.FunctionalTests/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "",
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
/// Summary description for DataContractSerializerController
/// </summary>
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);
}
}
}
19 changes: 19 additions & 0 deletions test/WebSites/FormatterWebSite/Models/Person.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}

0 comments on commit b9f1586

Please sign in to comment.