Avoid `InvalidOperationException` when serializing `SerializableError`

- #8055
- provide unique name (`<Empty-Key>`) for XML elements that would otherwise be nameless

nits:
- remove now-useless Mono special case in updated test class
- extend updated tests to involve square brackets as well as empty keys
This commit is contained in:
Doug Bunting 2018-07-14 15:45:12 -07:00
parent f31ab716ee
commit 498fa2d72f
No known key found for this signature in database
GPG Key ID: 888B4EB7822B32E9
2 changed files with 52 additions and 16 deletions

View File

@ -14,6 +14,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
[XmlRoot("Error")]
public sealed class SerializableErrorWrapper : IXmlSerializable, IUnwrappable
{
// Element name used when ModelStateEntry's Key is empty. Dash in element name should avoid collisions with
// other ModelState entries because the character is not legal in an expression name.
private static readonly string EmptyKey = "MVC-Empty";
// Note: XmlSerializer requires to have default constructor
public SerializableErrorWrapper()
{
@ -63,6 +67,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
var key = XmlConvert.DecodeName(reader.LocalName);
var value = reader.ReadInnerXml();
if (string.Equals(EmptyKey, key, StringComparison.Ordinal))
{
key = string.Empty;
}
SerializableError.Add(key, value);
reader.MoveToContent();
@ -81,6 +89,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
var key = keyValuePair.Key;
var value = keyValuePair.Value;
if (string.IsNullOrEmpty(key))
{
key = EmptyKey;
}
writer.WriteStartElement(XmlConvert.EncodeLocalName(key));
if (value != null)
{
@ -102,4 +115,4 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
return SerializableError;
}
}
}
}

View File

@ -6,7 +6,6 @@ using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
@ -28,8 +27,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
public void WrappedSerializableErrorInstance_ReturnedFromProperty()
{
// Arrange
var serializableError = new SerializableError();
serializableError.Add("key1", "key1-error");
var serializableError = new SerializableError
{
{ "key1", "key1-error" }
};
// Act
var wrapper = new SerializableErrorWrapper(serializableError);
@ -57,7 +58,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
{
// Arrange
var serializableErrorXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<Error><key1>Test Error 1 Test Error 2</key1><key2>Test Error 3</key2></Error>";
"<Error><MVC-Empty>Test error 0</MVC-Empty>" +
"<key1>Test Error 1 Test Error 2</key1>" +
"<key2>Test Error 3</key2>" +
"<list_x005B_3_x005D_.key3>Test Error 4</list_x005B_3_x005D_.key3></Error>";
var serializer = new DataContractSerializer(typeof(SerializableErrorWrapper));
// Act
@ -66,8 +70,28 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
var errors = wrapper.SerializableError;
// Assert
Assert.Equal("Test Error 1 Test Error 2", errors["key1"]);
Assert.Equal("Test Error 3", errors["key2"]);
Assert.Collection(
errors,
kvp =>
{
Assert.Equal(string.Empty, kvp.Key);
Assert.Equal("Test error 0", kvp.Value);
},
kvp =>
{
Assert.Equal("key1", kvp.Key);
Assert.Equal("Test Error 1 Test Error 2", kvp.Value);
},
kvp =>
{
Assert.Equal("key2", kvp.Key);
Assert.Equal("Test Error 3", kvp.Value);
},
kvp =>
{
Assert.Equal("list[3].key3", kvp.Key);
Assert.Equal("Test Error 4", kvp.Value);
});
}
[Fact]
@ -75,11 +99,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
{
// Arrange
var modelState = new ModelStateDictionary();
modelState.AddModelError(string.Empty, "Test error 0");
modelState.AddModelError("key1", "Test Error 1");
modelState.AddModelError("key1", "Test Error 2");
modelState.AddModelError("key2", "Test Error 3");
modelState.AddModelError("list[3].key3", "Test Error 4");
var serializableError = new SerializableError(modelState);
var outputStream = new MemoryStream();
var expectedContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<Error><MVC-Empty>Test error 0</MVC-Empty>" +
"<key1>Test Error 1 Test Error 2</key1>" +
"<key2>Test Error 3</key2>" +
"<list_x005B_3_x005D_.key3>Test Error 4</list_x005B_3_x005D_.key3></Error>";
// Act
using (var xmlWriter = XmlWriter.Create(outputStream))
@ -91,15 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd();
// Assert
var expectedContent =
TestPlatformHelper.IsMono ?
"<?xml version=\"1.0\" encoding=\"utf-8\"?><Error xmlns:i=\"" +
"http://www.w3.org/2001/XMLSchema-instance\"><key1>Test Error 1 Test Error 2</key1>" +
"<key2>Test Error 3</key2></Error>" :
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<Error><key1>Test Error 1 Test Error 2</key1><key2>Test Error 3</key2></Error>";
Assert.Equal(expectedContent, res);
}
}
}
}