I have a XDocument inside a controller that I want to server as xml and json (depending on the Accept header of the request).
I am using dotnet core:
In my startup.cs/ConfigureServices I have this:
services.AddMvc().AddXmlDataContractSerializerFormatters();
My controller essentially is this:
public async Task<IActionResult> getData(int id)
{
XDocument xmlDoc = db.getData(id);
return Ok(xmlDoc);
}
When making a request with Accept: application/json
, I get my data properly formatted as JSON. When making a request with Accept: application/xml
, I still get a JSON response (same as with application/json
).
I have also tried with:
services.AddMvc().AddXmlSerializerFormatters();
but that was even worse as even normal objects were served as JSON (XmlDataContractSerializer could handle normal objects, but not XDocument).
When I add [Produces("application/xml")]
to the controller (using AddXmlSerializerFormatters
), I get a Http 406 error when serving XDocument, but I do get a XML output when returning normal objects.
Do I have to convert the XDocument to objects to output XML from a controller? Is there an easy way to convert XDocuments to objects?
I was able to reproduce the described problem and after reading some source code in the ASP.NET Core GitHub repository (https://github.com/aspnet/Mvc/tree/dev/src/Microsoft.AspNetCore.Mvc.Formatters.Xml) there is a missing feature in the Xml formatters project. While the JSON formatter handles XDocument values amazingly well, the xml formatter tries to serialize the XDocument instance although not all objects are serializable. Enabling the XmlSerializerOutputFormatter to pass through the XmlData (simply by writing the string representation on the stream) would solve the root cause.
Therefore a quick and rather simple/naive workaround is to return a plain ContentResult (if content negotiation is not a strict requirement), like
return new ContentResult
{
Content = xmlDoc.ToString(),
ContentType = "text/xml",
StatusCode = 200
};
instead of
return Ok(xmlDoc);
In order to solve the root cause I suggest a feature request in the https://github.com/aspnet/Mvc Repo.
I solved the issue using the source code for the XmlDataContractSerializerOutputFormatter
and substituted WriteResponseBodyAsync
with this (5 lines including comment added):
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (selectedEncoding == null)
{
throw new ArgumentNullException(nameof(selectedEncoding));
}
var writerSettings = WriterSettings.Clone();
writerSettings.Encoding = selectedEncoding;
// Wrap the object only if there is a wrapping type.
var value = context.Object;
var wrappingType = GetSerializableType(context.ObjectType);
if (wrappingType != null && wrappingType != context.ObjectType)
{
var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext(
declaredType: context.ObjectType,
isSerialization: true));
value = wrapperProvider.Wrap(value);
}
var dataContractSerializer = GetCachedSerializer(wrappingType);
using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding))
{
using (var xmlWriter = CreateXmlWriter(textWriter, writerSettings))
{
// If XDocument, use its own serializer as DataContractSerializer cannot handle XDocuments.
if (value is XDocument)
{
((XDocument)value).WriteTo(xmlWriter);
}
else
dataContractSerializer.WriteObject(xmlWriter, value);
}
// Perf: call FlushAsync to call WriteAsync on the stream with any content left in the TextWriter's
// buffers. This is better than just letting dispose handle it (which would result in a synchronous
// write).
await textWriter.FlushAsync();
}
}
I am not completely satisfied with this solution, but it does allow the honoring of the Accept
header and produces either JSON or XML when given an XDocument
. If the XDocument
is inside on object, it will not be caught. That would mean rewriting DataContractSerializer, something I'd rather not do.
Strange thing is that on Microsofts own documentation DataContractSerializer
should be able to handle XDocument
:
https://msdn.microsoft.com/en-us/library/ms731923(v=vs.110).aspx
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With