简体   繁体   中英

How do I generate a SOAP-compatible XML response with correct namespace prefixes?

I'm writing a small web server (HttpListener) which will ultimately run as part of a Windows service and response to SOAP requests from another application.

I've written the code to decode the SOAP request XML and extract the action, process it and get the result, but can't quite get the response XML to be generated correctly.

I've like to avoid generating each element individually since the Types of the response may vary and I don't want to have to code every variant into the web server and would prefer not to have to delve into Reflection and walk the Type structure to output the values. I'd prefer to use something simple like the XmlSerializer Serialize method (which presumably is walking the Type structure), but it's not clear if it has enough control.

The output I'm trying to produce in my test program is:

<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>

The output I'm getting is:

<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>

This is the current test program:

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

Can this be done with Serialize or do have to do it with Reflection and, effectively, reproduce what the SOAP responder in IIS is already doing?

FYI, I also tried setting up a Type mapping...

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

... but the resultant XML was totally different and, although it didn't produce an error, also didn't decode in the calling application; a null value was returned

<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>

I like to use xml linq. For complicated headers a namespaces I just parse a string to get desired results. See code below :

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

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM