【问题标题】:Getting XML from stored procedure从存储过程中获取 XML
【发布时间】:2016-08-04 20:30:15
【问题描述】:

我正在从存储过程中获取 XML 的输出。我要做的是获取该 XML 并通过 ASP.NET 将其传递出去:

public XmlDocument GetPunchListXml(string communityDesc)
{
    try
    {
        using (connection = new SqlConnection(connectionString))
        {
             connection.Open();

             using (SqlCommand command = new SqlCommand("GetPunchList", connection))
             {
                   command.CommandType = CommandType.StoredProcedure;

                   SqlParameter parameter1 = new SqlParameter("@communityDesc", SqlDbType.VarChar);
                   parameter1.Value = communityDesc;
                   parameter1.Direction = ParameterDirection.Input;
                   command.Parameters.Add(parameter1);

                   var doc = new XmlDocument();
                   var reader = command.ExecuteXmlReader();
                   if (reader.Read())
                   {
                       doc.Load(reader);
                   }

                   return doc;
               }
           }
      }
      finally
      {
         connection.Close();
      }
}

但我不断收到这些错误:

<Error>
    <Message>An error has occurred.</Message>
    <ExceptionMessage>
        The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
    </ExceptionMessage>
    <ExceptionType>System.InvalidOperationException</ExceptionType>
    <StackTrace/>
    <InnerException>
        <Message>An error has occurred.</Message>
        <ExceptionMessage>
            Type 'System.Xml.XmlDocument' is an invalid collection type since it does not have a valid Add method with parameter of type 'System.Object'.
        </ExceptionMessage>
        <ExceptionType>
            System.Runtime.Serialization.InvalidDataContractException
        </ExceptionType>
        <StackTrace>
            at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.ThrowInvalidDataContractException(String message, Type type)
            at WriteArrayOfanyTypeToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract )
            at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
            at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
            at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
            at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
            at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
            at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
            at System.Runtime.Serialization.DataContractSerializer.WriteObject(XmlWriter writer, Object graph)
            at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content)
            at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)
                --- End of stack trace from previous location where exception was thrown ---
            at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
            at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
            at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
            at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()
        </StackTrace>
    </InnerException>
</Error>

我做错了什么?我尝试获取 XML 的方式有问题还是 XML 本身有问题?

我尝试了以下方法:

var doc = new XmlDocument();
string s = "";
using (XmlReader reader = command.ExecuteXmlReader())
{
    while (reader.Read())
    {
        //doc.Load(reader);
        s = reader.ReadOuterXml();
        doc.LoadXml(s);
    }
}
return doc;

并得到这些错误:

<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace/>
<InnerException>
<Message>An error has occurred.</Message>
<ExceptionMessage>
Type 'System.Xml.XmlDocument' is an invalid collection type since it does not have a valid Add method with parameter of type 'System.Object'.
</ExceptionMessage>
<ExceptionType>
System.Runtime.Serialization.InvalidDataContractException
</ExceptionType>
<StackTrace>
at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.ThrowInvalidDataContractException(String message, Type type) at WriteArrayOfanyTypeToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract ) at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle) at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver) at System.Runtime.Serialization.DataContractSerializer.WriteObject(XmlWriter writer, Object graph) at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content) at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()
</StackTrace>
</InnerException>
</Error>

【问题讨论】:

  • 您应该能够按照您的方式进行操作,但是出于调试目的,我会尝试首先将 XML 作为字符串从 XmlReader 中取出,以便您可以输出什么它返回,然后您可以使用 XmlDocument 上的 LoadXml() 方法尝试将 XML 作为字符串加载。首先看看它是否以这种方式工作,然后找出您的 Load() 方法方法出了什么问题。此外,您应该将您的阅读器包装在 using 语句中:using (var reader = command.ExecuteXmlReader()) { // Do stuff here }
  • @ManoDestra 我尝试了返回字符串的建议,它有效,但我真的需要它是 XML 格式。
  • 这完全取决于您可以通过 ASP.Net 序列化什么。如果可以的话,尝试将您的 XmlDocument 注入响应中,然后只需调用 XmlDocument 上的一个简单属性,如 document.OuterXml 并将该字符串放入一个 textarea 中,类似的东西。如果你能做那个简单的事情,那么序列化就很好了,你的代码的其他部分是错误的。对我来说,您的代码似乎在某个地方需要一个列表,而您正在给它一个 XmlDocument 来使用。如果没有看到执行注入的代码和您的视图代码,恐怕我不能说更多。
  • Type 'System.Xml.XmlDocument' is an invalid collection type since it does not have a valid Add method with parameter of type 'System.Object'. 这告诉我,有一个类型转换失败了。特别是您的doc.Load(reader); 代码。我认为(如果我错了,请纠正我)你需要指定你在阅读器中寻找的值的索引,就像你如何使用标准数据阅读器一样?如果是这样,你会想要这样的东西:doc.Load(reader(0));

标签: c# asp.net sql-server xml


【解决方案1】:

您可以在ASP.NET Web API 中创建自己的简单MediaTypeFormatter 以将XmlDocument 直接流式传输到响应。示例实现在此答案的末尾。

在使用 VS 向导创建新项目时获得的自动生成的 WebApiConfig 类的 public static void Register(HttpConfiguration config) 方法中注册它。

// This custom media type converter must be inserted before the default media type converter that handles application/xml, do not use config.Formatters.Add(...
config.Formatters.Insert(0, new MediaTypeFormatters.XmlDocumentMediaTypeFormatter());

在控制器类方法中做类似的事情

private HttpResponseMessage Get()
{
    XmlDocument doc = new XmlDocument();
    XmlDeclaration dec = doc.CreateXmlDeclaration("1.0", "utf-8", null);
    doc.AppendChild(dec);
    XmlElement rootElement = doc.CreateElement("user");
    doc.AppendChild(rootElement);
    rootElement.SetAttribute("id", "1");

    var response = Request.CreateResponse(HttpStatusCode.OK, doc, "application/xml");
    return (response);
}

示例自定义 MediaTypeFormatter 以将 XmlDocument 流式传输到响应。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Net;
using System.Xml;

namespace Take5.MediaTypeFormatters
{
    public class XmlDocumentMediaTypeFormatter : MediaTypeFormatter
    {
        private static Type _supportedType = typeof(XmlDocument);

        public XmlDocumentMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
        }

        public override bool CanReadType(Type type)
        {
            return type == _supportedType;
        }

        public override bool CanWriteType(Type type)
        {
            return type == _supportedType;
        }

        public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
        {
            var taskSource = new TaskCompletionSource<object>();
            try
            {
                System.Diagnostics.Debug.Assert(false, "Not fully implemented, get doc to read from readStream.");
                var doc = new XmlDocument();
                taskSource.SetResult(doc);
            }
            catch (Exception e)
            {
                taskSource.SetException(e);
            }
            return taskSource.Task;
        }

        public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
        {
            var taskSource = new TaskCompletionSource<object>();
            try
            {
                XmlDocument doc = (XmlDocument)value;
                XmlWriter writer = XmlWriter.Create(writeStream, new XmlWriterSettings() { Indent = true });
                doc.WriteContentTo(writer);
                writer.Flush();

                taskSource.SetResult(null);
            }
            catch (Exception e)
            {
                taskSource.SetException(e);
            }
            return taskSource.Task;
        }
    }
}

【讨论】:

    【解决方案2】:

    问题可能是您使用XmlDocument 的结果,因为我也找不到代码有任何问题。至于我为什么相信这个

    1. 您的堆栈跟踪都没有显示对您发布的代码的任何调用。他们实际上指出了一个从 HttpControllerHandler 开始的异常,因此它可能与您在何处或如何提供响应有关。
    2. 根据文档,调用SqlCommand.ExecuteXmlReader 可以抛出InvalidOperationException,但它读到只有在The SqlConnection closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. 时才会发生这种情况
    3. 根据文档,调用 XmlDocument.Load 只会引发异常 XmlException,因此根据您收到的异常,这不会是一个可能的故障点。
    4. 在谷歌搜索异常消息后,我发现 previous users had experienced this error 由于 Web API 配置问题,因此再次与从数据库中加载 Xml 无关。

    解决方案的推测

    这是推测,因为我不知道您使用的是什么框架,或者这是 WebApi 方法还是在调用堆栈中进一步提供结果的方法。

    因此,您的解决方法是将您的数据转换为可以由您使用的任何框架序列化的格式。如需更多帮助,您需要提供。

    1. 您使用的是什么框架(web api、mvc 控制器、asp.net 等)
    2. 如果 web Api - 正在调用的提供数据的方法端点。如果这是该方法,那么错误是您试图直接返回 XmlDocument。
      • 确保在您的WebApiConfig.cs 文件中配置了正确的格式化程序。 GlobalConfiguration.Configuration.Formatters.Add(new System.Net.Http.Formatting.XmlMediaTypeFormatter()); GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
      • 请参阅this previous SO answer,了解如何更改工作方法。
      • 我建议保持原样并将其标记为私有,然后添加调用此方法的 Api 方法并将结果格式化为可以传递回客户端的内容。

    Web API 修复

    我可以通过在 Web API 控制器中返回 XmlDocument 作为响应来复制确切的错误和消息。这再次说明它与方法的内容无关,但更多的是响应中使用的 XmlDocument。如果您在响应中直接返回 XmlDocument(因此不是较大对象的一部分),您可以通过添加一个知道如何反序列化(提取实际 Xml 内容)XmlDocument 的新 HttpContent 类型来修复它.这是基于Darrel Miller 之前发布的SO answer。这是一个完整的独立示例中的修复。

    using System.IO;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using System.Web.Http;
    using System.Xml;
    
    namespace WebApiTests.Controllers
    {
        public class HomeController : ApiController
        {
            const string sampleData = "<?xml version=\"1.0\"?><catalog><book id=\"bk101\"><author>Gambardella, Matthew</author><title>XML Developer's Guide</title><genre>Computer</genre><price>44.95</price><publish_date>2000-10-01</publish_date><description>An in-depth look at creating applications with XML.</description></book></catalog>";
            public HttpResponseMessage Get()
            {
                var doc = new XmlDocument();
                doc.LoadXml(sampleData);
    
                return new HttpResponseMessage()
                {
                    RequestMessage = Request,
                    Content = new XmlContent(doc)
                };
            }
        }
    
        public class XmlContent : HttpContent
        {
            private readonly MemoryStream _Stream = new MemoryStream();
    
            public XmlContent(XmlDocument document)
            {
                document.Save(_Stream);
                _Stream.Position = 0;
                Headers.ContentType = new MediaTypeHeaderValue("application/xml");
            }
    
            protected override Task SerializeToStreamAsync(Stream stream, System.Net.TransportContext context)
            {
                _Stream.CopyTo(stream);
                var tcs = new TaskCompletionSource<object>();
                tcs.SetResult(null);
                return tcs.Task;
            }
    
            protected override bool TryComputeLength(out long length)
            {
                length = _Stream.Length;
                return true;
            }
    
            protected override void Dispose(bool disposing)
            {
                if(_Stream != null)
                    _Stream.Dispose();
            }
        }
    }
    

    一般代码修复

    您的方法确实存在一些问题,因为它与SqlConnection 有关。下面是清理后的代码和解释。

    public XmlDocument GetPunchListXml(string communityDesc)
    {
        // 1. Use a new SqlConnection everywhere and do not register SqlConnection as a field on the class.
        // This is a Microsoft recommended best practice. Sql Server handles connection pooling so the call new SqlConnection is very cheap.
        // 2. a using block will close and dispose the connection for you
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            connection.Open();
    
            using (SqlCommand command = new SqlCommand("GetPunchList", connection))
            {
                command.CommandType = CommandType.StoredProcedure;
    
                SqlParameter parameter1 = new SqlParameter("@communityDesc", SqlDbType.VarChar);
                parameter1.Value = communityDesc;
                parameter1.Direction = ParameterDirection.Input;
                command.Parameters.Add(parameter1);
    
                // wrap your DataReader in a using block
                using (var reader = command.ExecuteXmlReader())
                {
                    var doc = new XmlDocument();
                    if (reader.Read())
                    {
                       doc.Load(reader);
                    }
                    return doc;
                }
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      XmlDocument 不可序列化,而是返回 XML 的字符串表示

      using (var stringWriter = new StringWriter())
      using (var xmlTextWriter = XmlWriter.Create(stringWriter))
      {
          xmlDoc.WriteTo(xmlTextWriter);
          xmlTextWriter.Flush();
          return stringWriter.GetStringBuilder().ToString();
      }
      

      【讨论】:

        【解决方案4】:

        更改以下代码,

        var doc = new XmlDocument();
        var reader = command.ExecuteXmlReader();
        if (reader.Read())
        {
           doc.Load(reader);
        }
        

        如下,

        XmlDocument doc = new XmlDocument();
        XmlReader reader = command.ExecuteXmlReader();
        if (reader.Read())
        {
            doc.Load(reader);
        }
        

        如果还是不行,请分享你从SP返回的xml。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-02-28
          • 2017-01-01
          • 2016-10-31
          • 2018-03-04
          • 2011-04-26
          • 2012-07-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多