【问题标题】:Returning XDocument from Controller (dotnet coreclr)从控制器返回 XDocument (dotnet coreclr)
【发布时间】:2016-07-12 08:30:27
【问题描述】:

我在控制器中有一个 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?有没有一种简单的方法可以将 XDocument 转换为对象?

【问题讨论】:

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


    【解决方案1】:

    我能够重现所描述的问题,并且在阅读了 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 Repo 中提出功能请求。

    【讨论】:

    • 请求可能应该在此处提交:github.com/dotnet/corefx 因为 MVC 仅在后端使用序列化程序并依赖它们来指示它们是否可以编写类型。对吗?
    • 您的解决方案似乎有效,谢谢,但我希望同时提供 json 和 xml 结果。我在每个控制器中有许多控制器和许多端点,它们必须提供基于 XDocument 的 xml/json。手动检查每个控制器/端点中的 Accept 标头有点多。我将看看修改 Xml 输出格式化程序
    【解决方案2】:

    我使用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

    【讨论】:

    • 您可以实现一个 IWrapperProviderFactory 来用一个实现 IXMLSerializable 的简单对象来包装 XDocument,而不是更改输出序列化器。
    猜你喜欢
    • 1970-01-01
    • 2018-08-19
    • 1970-01-01
    • 2016-01-21
    • 2012-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多