【问题标题】:How do I generate a SOAP-compatible XML response with correct namespace prefixes?如何生成具有正确命名空间前缀的 SOAP 兼容 XML 响应?
【发布时间】:2019-07-05 17:04:35
【问题描述】:

我正在编写一个小型 Web 服务器 (HttpListener),它最终将作为 Windows 服务的一部分运行并响应来自另一个应用程序的 SOAP 请求。

我已经编写了代码来解码 SOAP 请求 XML 并提取操作,对其进行处理并获得结果,但不能完全正确生成响应 XML。

我想避免单独生成每个元素,因为响应的类型可能会有所不同,并且我不想将每个变体都编码到 Web 服务器中,并且不想深入研究反射和遍历输入结构以输出值。我更喜欢使用像 XmlSerializer Serialize 方法这样简单的东西(大概是遍历 Type 结构),但不清楚它是否有足够的控制权。

我试图在我的测试程序中产生的输出是:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <GetUsernamesResponse xmlns="http://tempuri.org/">
      <GetUsernamesResult xmlns:a="http://schemas.datacontract.org/2004/07/ConsoleApp2"
            xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <a:Results xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
          <b:string>Kermit.The.Frog</b:string>
          <b:string>Miss.Piggy</b:string>
        </a:Results>
      </GetUsernamesResult>
    </GetUsernamesResponse>
  </s:Body>
</s:Envelope>

我得到的输出是:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <GetUsernamesResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" 
            xmlns="http://tempuri.org/">
      <GetUsernamesResult xmlns:a="http://schemas.datacontract.org/2004/07/ConsoleApp2" 
                xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/Arrays" 
                xmlns="">
        <Results>
          <string>Kermit.The.Frog</string>
          <string>Miss.Piggy</string>
        </Results>
      </GetUsernamesResult>
    </GetUsernamesResponse>
  </s:Body>
</s:Envelope>

这是当前的测试程序:

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace ConsoleApp2
{
    public class GetUsernamesResponse
    {
        public List<string> Results { get; set; }
    }

    public class GetUsernamesResult : GetUsernamesResponse {}

    public class Program
    {
        private const string ns_t = "http://tempuri.org/";
        private const string ns_s = "http://schemas.xmlsoap.org/soap/envelope/";
        private const string ns_i = "http://www.w3.org/2001/XMLSchema-instance";
        private const string ns_a = "http://schemas.datacontract.org/2004/07/ConsoleApp2";
        private const string ns_b = "http://schemas.microsoft.com/2003/10/Serialization/Arrays";

        private static void Main(string[] args)
        {
            var r = new GetUsernamesResult()
            {
                Results = new List<string>
                {
                    "Kermit.The.Frog",
                    "Miss.Piggy"
                }
            };

            var ns = new XmlSerializerNamespaces();
            ns.Add("i", ns_i);
            ns.Add("a", ns_a);
            ns.Add("b", ns_b);

            var oSerializer = new XmlSerializer(typeof(GetUsernamesResult));
            using (var sw = new StringWriter())
            {
                var xw = XmlWriter.Create(
                    sw,
                    new XmlWriterSettings()
                    {
                        OmitXmlDeclaration = true,
                        Indent = true,
                        ConformanceLevel = ConformanceLevel.Fragment,
                        NamespaceHandling = NamespaceHandling.OmitDuplicates,
                    });
                xw.WriteStartElement("s", "Envelope", ns_s);
                xw.WriteStartElement("s", "Body", ns_s);
                xw.WriteStartElement($"GetUsernamesResponse", ns_t);
                xw.WriteAttributeString("xmlns", "i", null, ns_i);
                oSerializer.Serialize(xw, r, ns);
                xw.WriteEndElement();
                xw.WriteEndElement();
                xw.WriteEndElement();
                xw.Close();
                Console.WriteLine(sw);
            }
            Console.ReadKey();
        }
    }
}

这可以通过 Serialize 来完成,还是必须通过 Reflection 来完成,并且有效地重现 IIS 中的 SOAP 响应程序已经在做的事情?

仅供参考,我也尝试设置类型映射...

var mapping = new SoapReflectionImporter().ImportTypeMapping(typeof(BarcodeProductionGetUsernamesResult));
var oSerializer = new XmlSerializer(mapping);

...但是生成的 XML 完全不同,虽然它没有产生错误,但也没有在调用应用程序中解码;返回了一个空值

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <GetUsernamesResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://tempuri.org/">
      <GetUsernamesResult xmlns:a="http://schemas.datacontract.org/2004/07/ConsoleApp2" xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/Arrays" id="id1" xmlns="">
      <Results href="#id2" />
    </GetUsernamesResult>
    <q1:Array id="id2" xmlns:q2="http://www.w3.org/2001/XMLSchema" q1:arrayType="q2:string[2]" xmlns:q1="http://schemas.xmlsoap.org/soap/encoding/">
        <Item xmlns="">Kermit.The.Frog</Item>
        <Item xmlns="">Miss.Piggy</Item>
      </q1:Array>
    </GetUsernamesResponse>
  </s:Body>
</s:Envelope>

【问题讨论】:

  • 您没有使用 ns_a 或 ns_b 编写任何元素,这就是为什么输出都不包含 a: 或 b:。
  • 我意识到这一点并且得出的结论是,除非对象/类已经用 XmlElement 属性进行了修饰,否则唯一的解决方案是使用反射实际遍历结构并显式生成元素。似乎没有自动机制来识别数组/列表/集合等。

标签: c# xml soap


【解决方案1】:

我喜欢使用 xml linq。对于复杂的标题和命名空间,我只需解析一个字符串即可获得所需的结果。请参阅下面的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = new MyClass();

            XDocument doc = MySerializer<MyClass>.GetXElement(myClass);
        }
    }

    public class MySerializer<T> where T : new()
    {
        static string xml =
           "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
           "    <s:Body>" +
           "       <GetUsernamesResponse xmlns=\"http://tempuri.org/\">" +
           "          <GetUsernamesResult xmlns:a=\"http://schemas.datacontract.org/2004/07/ConsoleApp2\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
           "             <a:Results xmlns:b=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">" +
           "             </a:Results>" +
           "          </GetUsernamesResult>" +
           "      </GetUsernamesResponse>" +
           "   </s:Body>" +
           "</s:Envelope>";
        public static XDocument GetXElement(T myClass)
        {


            XDocument doc = XDocument.Parse(xml);

            XElement results = doc.Descendants().Where(x => x.Name.LocalName == "Results").FirstOrDefault();
            XNamespace ns_b = results.GetNamespaceOfPrefix("b");

            StringWriter sWriter = new StringWriter();
            XmlWriter xWriter = XmlWriter.Create(sWriter);

            XmlSerializerNamespaces ns1 = new XmlSerializerNamespaces();
            ns1.Add("b", ns_b.NamespaceName);

            XmlSerializer serializer = new XmlSerializer(typeof(T), ns_b.NamespaceName);
            serializer.Serialize(xWriter, myClass, ns1);
            results.Add(XElement.Parse(sWriter.ToString()));

            return doc;
        }

    }
    public class MyClass
    {
        public string test { get; set; }
    }

}

【讨论】:

  • 不幸的是,这是特定于类/类型的,因为您已经包含了需要生成的 XML。正如我所提到的,实际的应用程序可能会使用几种不同的类型 [事实上,调用是var oXmlSerializer = new XmlSerializer(responseType)] 所以我没有具体的结构,希望避免为每种不同的类型设置一个部分;毕竟,IIS 中的 SOAP 响应程序会为它看到的任何类型执行此操作。我想我将不得不深入研究反射并重新发明 IIS 中必须存在的“轮子”。 :)
  • 我认为响应标头是相同的,只有结果会不同。您可以在我的代码中包装来自唯一操作的 xml 结果。
  • 添加响应头不是问题。我遇到的问题是如何遍历类结构(可以是十多个类中的任何一个)。 “真实”程序中的关键语句是var method = typeof(IMyService).GetMethods().Where(m =&gt; m.Name.Equals(action)).First();,然后是var methResult = method.ReturnType;,然后是var xResult = Convert.ChangeType(method.Invoke(_myService, new object[] { requestObj })),methResult);。所以在生成最终的XML 时,所知道的只是xResult 的类型包含在methResult 中,但是它几乎可以是任何东西
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-03
  • 1970-01-01
  • 1970-01-01
  • 2021-08-05
  • 1970-01-01
  • 2012-08-13
相关资源
最近更新 更多