从Controller返回XDocument(dotnet coreclr)

时间:2016-07-12 08:30:28

标签: .net xml controller asp.net-core asp.net-core-mvc

我在控制器中有一个XDocument,我希望将其作为xml和json服务器(取决于请求的Accept标头)。

我使用的是dotnet核心:

在我的startup.cs / ConfigureServices中我有:

services.AddMvc().AddXmlDataContractSerializerFormatters();

我的控制器基本上是这样的:

public async Task<IActionResult> getData(int id)
{
    XDocument xmlDoc = db.getData(id);
    return Ok(xmlDoc);
}

使用Accept: application/json发出请求时,我的数据格式正确为JSON格式。在使用Accept: application/xml发出请求时,我仍然会收到JSON响应(与application/json相同)。

我也尝试过:

services.AddMvc().AddXmlSerializerFormatters();

但更糟糕的是,甚至普通对象都被用作JSON(XmlDataContractSerializer可以处理普通对象,但不能处理XDocument)。

当我向控制器添加[Produces("application/xml")]时(使用AddXmlSerializerFormatters),在提供XDocument时出现Http 406错误,但在返回普通对象时确实得到了XML输出。

我是否必须将XDocument转换为对象以从控制器输出XML?有没有简单的方法将XDocuments转换为对象?

2 个答案:

答案 0 :(得分:4)

我能够重现所描述的问题,在阅读了ASP.NET Core GitHub存储库(https://github.com/aspnet/Mvc/tree/dev/src/Microsoft.AspNetCore.Mvc.Formatters.Xml)中的一些源代码后,Xml格式化程序项目中缺少一项功能。虽然JSON格式化程序处理XDocument值非常好,但xml格式化程序尝试序列化XDocument实例,尽管并非所有对象都可序列化。 启用XmlSerializerOutputFormatter以通过XmlData(只需在流上写入字符串表示)将解决根本原因。

因此,快速而简单/天真的解决方法是返回一个简单的ContentResult(如果内容协商不是严格的要求),例如

return new ContentResult
            {
                Content = xmlDoc.ToString(),
                ContentType = "text/xml",
                StatusCode = 200
            };

而不是

  return Ok(xmlDoc);

为了解决根本原因,我建议在https://github.com/aspnet/Mvc回购中提出功能请求。

答案 1 :(得分:1)

我使用XmlDataContractSerializerOutputFormatter的源代码解决了这个问题,并用此替换了WriteResponseBodyAsync(包括添加了评论的5行):

    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();
        }
    }

我对此解决方案并不完全满意,但它确实允许尊重Accept标头,并在给定XDocument时生成JSON或XML。如果XDocument在对象内部,则不会被捕获。这意味着重写DataContractSerializer,我宁愿不这样做。

奇怪的是,在微软上,自己的文档DataContractSerializer应该能够处理XDocument

https://msdn.microsoft.com/en-us/library/ms731923(v=vs.110).aspx