Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit d5bb73a

Browse files
committed
[Fixes #6197] AddXmlSerializerFormatters with no namespace
1 parent 05d02e7 commit d5bb73a

File tree

5 files changed

+219
-36
lines changed

5 files changed

+219
-36
lines changed

src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,26 @@ public virtual XmlWriter CreateXmlWriter(
203203
return XmlWriter.Create(writer, xmlWriterSettings);
204204
}
205205

206+
/// <summary>
207+
/// Creates a new instance of <see cref="XmlWriter"/> using the given <see cref="TextWriter"/> and
208+
/// <see cref="XmlWriterSettings"/>.
209+
/// </summary>
210+
/// <param name="context">The formatter context associated with the call.</param>
211+
/// <param name="writer">
212+
/// The underlying <see cref="TextWriter"/> which the <see cref="XmlWriter"/> should write to.
213+
/// </param>
214+
/// <param name="xmlWriterSettings">
215+
/// The <see cref="XmlWriterSettings"/>.
216+
/// </param>
217+
/// <returns>A new instance of <see cref="XmlWriter"/>.</returns>
218+
public virtual XmlWriter CreateXmlWriter(
219+
OutputFormatterWriteContext context,
220+
TextWriter writer,
221+
XmlWriterSettings xmlWriterSettings)
222+
{
223+
return CreateXmlWriter(writer, xmlWriterSettings);
224+
}
225+
206226
/// <inheritdoc />
207227
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
208228
{
@@ -235,7 +255,7 @@ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext co
235255

236256
using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding))
237257
{
238-
using (var xmlWriter = CreateXmlWriter(textWriter, writerSettings))
258+
using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings))
239259
{
240260
dataContractSerializer.WriteObject(xmlWriter, value);
241261
}

src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ protected virtual XmlSerializer CreateSerializer(Type type)
158158
/// <param name="xmlWriterSettings">
159159
/// The <see cref="XmlWriterSettings"/>.
160160
/// </param>
161-
/// <returns>A new instance of <see cref="XmlWriter"/></returns>
161+
/// <returns>A new instance of <see cref="XmlWriter"/>.</returns>
162162
public virtual XmlWriter CreateXmlWriter(
163163
TextWriter writer,
164164
XmlWriterSettings xmlWriterSettings)
@@ -179,6 +179,26 @@ public virtual XmlWriter CreateXmlWriter(
179179
return XmlWriter.Create(writer, xmlWriterSettings);
180180
}
181181

182+
/// <summary>
183+
/// Creates a new instance of <see cref="XmlWriter"/> using the given <see cref="TextWriter"/> and
184+
/// <see cref="XmlWriterSettings"/>.
185+
/// </summary>
186+
/// <param name="context">The formatter context associated with the call.</param>
187+
/// <param name="writer">
188+
/// The underlying <see cref="TextWriter"/> which the <see cref="XmlWriter"/> should write to.
189+
/// </param>
190+
/// <param name="xmlWriterSettings">
191+
/// The <see cref="XmlWriterSettings"/>.
192+
/// </param>
193+
/// <returns>A new instance of <see cref="XmlWriter"/></returns>
194+
public virtual XmlWriter CreateXmlWriter(
195+
OutputFormatterWriteContext context,
196+
TextWriter writer,
197+
XmlWriterSettings xmlWriterSettings)
198+
{
199+
return CreateXmlWriter(writer, xmlWriterSettings);
200+
}
201+
182202
/// <inheritdoc />
183203
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
184204
{
@@ -211,9 +231,9 @@ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext co
211231

212232
using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding))
213233
{
214-
using (var xmlWriter = CreateXmlWriter(textWriter, writerSettings))
234+
using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings))
215235
{
216-
xmlSerializer.Serialize(xmlWriter, value);
236+
Serialize(xmlSerializer, xmlWriter, value);
217237
}
218238

219239
// Perf: call FlushAsync to call WriteAsync on the stream with any content left in the TextWriter's
@@ -223,6 +243,18 @@ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext co
223243
}
224244
}
225245

246+
/// <summary>
247+
/// Serializes value using the passed in <paramref name="xmlSerializer"/> and <paramref name="xmlWriter"/>.
248+
/// </summary>
249+
/// <param name="xmlSerializer">The serializer used to serialize the <paramref name="value"/>.</param>
250+
/// <param name="xmlWriter">The writer used by the serializer <paramref name="xmlSerializer"/>
251+
/// to serialize the <paramref name="value"/>.</param>
252+
/// <param name="value">The value to be serialized.</param>
253+
protected virtual void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
254+
{
255+
xmlSerializer.Serialize(xmlWriter, value);
256+
}
257+
226258
/// <summary>
227259
/// Gets the cached serializer or creates and caches the serializer for the given type.
228260
/// </summary>
Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.IO;
56
using System.Threading;
7+
using System.Threading.Tasks;
68
using Moq;
79

810
namespace Microsoft.AspNetCore.Mvc
@@ -11,11 +13,20 @@ public static class FlushReportingStream
1113
{
1214
public static Stream GetThrowingStream()
1315
{
14-
var mock = new Mock<Stream>();
15-
mock.Verify(m => m.Flush(), Times.Never());
16-
mock.Verify(m => m.FlushAsync(It.IsAny<CancellationToken>()), Times.Never());
16+
return new NonFlushingStream();
17+
}
18+
19+
private class NonFlushingStream : MemoryStream
20+
{
21+
public override void Flush()
22+
{
23+
throw new InvalidOperationException("Flush should not have been called.");
24+
}
1725

18-
return mock.Object;
26+
public override Task FlushAsync(CancellationToken cancellationToken)
27+
{
28+
throw new InvalidOperationException("FlushAsync should not have been called.");
29+
}
1930
}
2031
}
2132
}

test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,42 @@ public void DoesNotThrow_OnNoLoggerAnd_WhenUnableToCreateSerializerForType()
682682
Assert.False(canWriteResult);
683683
}
684684

685+
public static TheoryData<bool, object, string> CanIndentOutputConditionallyData
686+
{
687+
get
688+
{
689+
var obj = new DummyClass { SampleInt = 10 };
690+
var newLine = Environment.NewLine;
691+
return new TheoryData<bool, object, string>()
692+
{
693+
{ true, obj, "<DummyClass xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
694+
$"{newLine} <SampleInt>10</SampleInt>{newLine}</DummyClass>" },
695+
{ false, obj, "<DummyClass xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
696+
"<SampleInt>10</SampleInt></DummyClass>" }
697+
};
698+
}
699+
}
700+
701+
[Theory]
702+
[MemberData(nameof(CanIndentOutputConditionallyData))]
703+
public async Task CanIndentOutputConditionally(bool indent, object input, string expectedOutput)
704+
{
705+
// Arrange
706+
var formatter = new IndentingXmlDataContractSerializerOutputFormatter();
707+
var outputFormatterContext = GetOutputFormatterContext(input, input.GetType());
708+
outputFormatterContext.HttpContext.Request.QueryString = new QueryString("?indent=" + indent);
709+
710+
// Act
711+
await formatter.WriteAsync(outputFormatterContext);
712+
713+
// Assert
714+
var body = outputFormatterContext.HttpContext.Response.Body;
715+
body.Position = 0;
716+
717+
var content = new StreamReader(body).ReadToEnd();
718+
Assert.Equal(expectedOutput, content);
719+
}
720+
685721
private OutputFormatterWriteContext GetOutputFormatterContext(
686722
object outputValue,
687723
Type outputType,
@@ -696,20 +732,12 @@ private OutputFormatterWriteContext GetOutputFormatterContext(
696732

697733
private static HttpContext GetHttpContext(string contentType)
698734
{
699-
var request = new Mock<HttpRequest>();
700-
701-
var headers = new HeaderDictionary();
702-
headers["Accept-Charset"] = MediaTypeHeaderValue.Parse(contentType).Charset.ToString();
703-
request.Setup(r => r.ContentType).Returns(contentType);
704-
request.SetupGet(r => r.Headers).Returns(headers);
705-
706-
var response = new Mock<HttpResponse>();
707-
response.SetupGet(f => f.Body).Returns(new MemoryStream());
708-
709-
var httpContext = new Mock<HttpContext>();
710-
httpContext.SetupGet(c => c.Request).Returns(request.Object);
711-
httpContext.SetupGet(c => c.Response).Returns(response.Object);
712-
return httpContext.Object;
735+
var httpContext = new DefaultHttpContext();
736+
var request = httpContext.Request;
737+
request.Headers["Accept-Charset"] = MediaTypeHeaderValue.Parse(contentType).Charset.ToString();
738+
request.ContentType = contentType;
739+
httpContext.Response.Body = new MemoryStream();
740+
return httpContext;
713741
}
714742

715743
private class TestXmlDataContractSerializerOutputFormatter : XmlDataContractSerializerOutputFormatter
@@ -723,6 +751,22 @@ protected override DataContractSerializer CreateSerializer(Type type)
723751
}
724752
}
725753

754+
private class IndentingXmlDataContractSerializerOutputFormatter : XmlDataContractSerializerOutputFormatter
755+
{
756+
public override XmlWriter CreateXmlWriter(
757+
OutputFormatterWriteContext context,
758+
TextWriter writer,
759+
XmlWriterSettings xmlWriterSettings)
760+
{
761+
var request = context.HttpContext.Request;
762+
if (request.Query["indent"] == "True")
763+
{
764+
xmlWriterSettings.Indent = true;
765+
}
766+
767+
return base.CreateXmlWriter(context, writer, xmlWriterSettings);
768+
}
769+
}
726770
public class Customer
727771
{
728772
public Customer(int id)

test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Xml;
1111
using System.Xml.Serialization;
1212
using Microsoft.AspNetCore.Http;
13+
using Microsoft.AspNetCore.Http.Features;
1314
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
1415
using Microsoft.Extensions.Logging;
1516
using Microsoft.Extensions.Logging.Testing;
@@ -71,6 +72,62 @@ public async Task XmlSerializerOutputFormatterCanWriteBasicTypes(object input, s
7172
XmlAssert.Equal(expectedOutput, content);
7273
}
7374

75+
public static TheoryData<bool, object, string> CanIndentOutputConditionallyData
76+
{
77+
get
78+
{
79+
var obj = new DummyClass { SampleInt = 10 };
80+
var newLine = Environment.NewLine;
81+
return new TheoryData<bool, object, string>()
82+
{
83+
{ true, obj, "<DummyClass xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
84+
$"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">{newLine} <SampleInt>10</SampleInt>{newLine}</DummyClass>" },
85+
{ false, obj, "<DummyClass xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
86+
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><SampleInt>10</SampleInt></DummyClass>" }
87+
};
88+
}
89+
}
90+
91+
[Theory]
92+
[MemberData(nameof(CanIndentOutputConditionallyData))]
93+
public async Task XmlSerializer_CanIndentOutputConditionally(bool indent, object input, string expectedOutput)
94+
{
95+
// Arrange
96+
var formatter = new IndentingXmlSerializerOutputFormatter();
97+
var outputFormatterContext = GetOutputFormatterContext(input, input.GetType());
98+
outputFormatterContext.HttpContext.Request.QueryString = new QueryString("?indent=" + indent);
99+
100+
// Act
101+
await formatter.WriteAsync(outputFormatterContext);
102+
103+
// Assert
104+
var body = outputFormatterContext.HttpContext.Response.Body;
105+
body.Position = 0;
106+
107+
var content = new StreamReader(body).ReadToEnd();
108+
Assert.Equal(expectedOutput, content);
109+
}
110+
111+
[Fact]
112+
public async Task XmlSerializer_CanModifyNamespacesInGeneratedXml()
113+
{
114+
// Arrange
115+
var input = new DummyClass { SampleInt = 10 };
116+
var formatter = new IgnoreAmbientNamespacesXmlSerializerOutputFormatter();
117+
var outputFormatterContext = GetOutputFormatterContext(input, input.GetType());
118+
var expectedOutput = "<DummyClass><SampleInt>10</SampleInt></DummyClass>";
119+
120+
// Act
121+
await formatter.WriteAsync(outputFormatterContext);
122+
123+
// Assert
124+
var body = outputFormatterContext.HttpContext.Response.Body;
125+
body.Position = 0;
126+
127+
var content = new StreamReader(body).ReadToEnd();
128+
Assert.Equal(expectedOutput, content);
129+
}
130+
74131
[Fact]
75132
public void XmlSerializer_CachesSerializerForType()
76133
{
@@ -461,20 +518,12 @@ private OutputFormatterWriteContext GetOutputFormatterContext(
461518

462519
private static HttpContext GetHttpContext(string contentType)
463520
{
464-
var request = new Mock<HttpRequest>();
465-
466-
var headers = new HeaderDictionary();
467-
headers["Accept-Charset"] = MediaTypeHeaderValue.Parse(contentType).Charset.ToString();
468-
request.Setup(r => r.ContentType).Returns(contentType);
469-
request.SetupGet(r => r.Headers).Returns(headers);
470-
471-
var response = new Mock<HttpResponse>();
472-
response.SetupGet(f => f.Body).Returns(new MemoryStream());
473-
474-
var httpContext = new Mock<HttpContext>();
475-
httpContext.SetupGet(c => c.Request).Returns(request.Object);
476-
httpContext.SetupGet(c => c.Response).Returns(response.Object);
477-
return httpContext.Object;
521+
var httpContext = new DefaultHttpContext();
522+
var request = httpContext.Request;
523+
request.Headers["Accept-Charset"] = MediaTypeHeaderValue.Parse(contentType).Charset.ToString();
524+
request.ContentType = contentType;
525+
httpContext.Response.Body = new MemoryStream();
526+
return httpContext;
478527
}
479528

480529
private class TestXmlSerializerOutputFormatter : XmlSerializerOutputFormatter
@@ -496,5 +545,32 @@ public Customer(int id)
496545

497546
public int MyProperty { get; set; }
498547
}
548+
private class IndentingXmlSerializerOutputFormatter : XmlSerializerOutputFormatter
549+
{
550+
public override XmlWriter CreateXmlWriter(
551+
OutputFormatterWriteContext context,
552+
TextWriter writer,
553+
XmlWriterSettings xmlWriterSettings)
554+
{
555+
var request = context.HttpContext.Request;
556+
if (request.Query["indent"] == "True")
557+
{
558+
xmlWriterSettings.Indent = true;
559+
}
560+
561+
return base.CreateXmlWriter(context, writer, xmlWriterSettings);
562+
}
563+
}
564+
565+
private class IgnoreAmbientNamespacesXmlSerializerOutputFormatter : XmlSerializerOutputFormatter
566+
{
567+
protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
568+
{
569+
var namespaces = new XmlSerializerNamespaces();
570+
namespaces.Add("", "");
571+
572+
xmlSerializer.Serialize(xmlWriter, value, namespaces);
573+
}
574+
}
499575
}
500576
}

0 commit comments

Comments
 (0)