Abrufen von RAW-Seifendaten von einem Web Reference Client, der in ASP.net ausgeführt wird

95

Ich versuche, einen Webdienst-Client in meinem aktuellen Projekt zu reparieren. Ich bin mir nicht sicher über die Plattform des Service Servers (höchstwahrscheinlich LAMP). Ich glaube, es gibt einen Fehler auf ihrer Seite des Zauns, da ich die potenziellen Probleme mit meinem Kunden beseitigt habe. Der Client ist ein Standard-Webreferenz-Proxy vom Typ ASMX, der automatisch aus der Service-WSDL generiert wird.

Was ich erreichen muss, sind die RAW-SOAP-Nachrichten (Anfrage und Antworten)

Was ist der beste Weg, um dies zu erreichen?

Andrew Harry
quelle

Antworten:

135

Ich habe die folgenden Änderungen vorgenommen web.config, um den SOAP-Umschlag (Request / Response) zu erhalten. Dadurch werden alle SOAP-Rohinformationen in die Datei ausgegeben trace.log.

<system.diagnostics>
  <trace autoflush="true"/>
  <sources>
    <source name="System.Net" maxdatasize="1024">
      <listeners>
        <add name="TraceFile"/>
      </listeners>
    </source>
    <source name="System.Net.Sockets" maxdatasize="1024">
      <listeners>
        <add name="TraceFile"/>
      </listeners>
    </source>
  </sources>
  <sharedListeners>
    <add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener"
      initializeData="trace.log"/>
  </sharedListeners>
  <switches>
    <add name="System.Net" value="Verbose"/>
    <add name="System.Net.Sockets" value="Verbose"/>
  </switches>
</system.diagnostics>
Keltex
quelle
2
Dies funktioniert bei mir im Jahr 02/2012 mit VS 2010 nicht. Kennt jemand eine aktuellere Lösung?
Qxotk
2
Das ist hilfreich. In meinem Fall sind die Antworten jedoch komprimiert und das Herausziehen des ASCII- oder des Hex-Codes aus der kombinierten Ausgabe ist ein Schmerz. Sind ihre alternativen Ausgabemethoden entweder binär oder nur Text?
2
@Dediqated - Sie können den Pfad zur Datei trace.log festlegen, indem Sie den Wert des Attributs initializeData im obigen Beispiel anpassen.
DougCouto
3
Es nützt nichts mehr, ich habe Wireshark verwendet, um die SOAP-Rohdaten zu sehen. Danke trotzdem!
Dediqated
7
Gut. Aber das XML ist für mich wie folgt: System.Net Verbose: 0: [14088] 00000000: 3C 3F 78 6D 6C 20 76 65-72 73 69 6F 6E 3D 22 31: <? Xml version = "1 System.Net Verbose: 0: [14088] 00000010: 2E 30 22 20 65 6E 63 6F-64 69 6E 67 3D 22 75 74: .0 "encoding =" ut ....... Irgendeine Idee, wie man es formatiert oder nur XML-Daten hat .?
Habeeb
35

Sie können eine SoapExtension implementieren, die die vollständige Anforderung und Antwort auf eine Protokolldatei protokolliert. Anschließend können Sie die SoapExtension in der Datei web.config aktivieren, wodurch das Ein- und Ausschalten für Debugging-Zwecke vereinfacht wird. Hier ist ein Beispiel, das ich für meinen eigenen Gebrauch gefunden und geändert habe. In meinem Fall wurde die Protokollierung von log4net durchgeführt, aber Sie können die Protokollierungsmethoden durch Ihre eigenen ersetzen.

public class SoapLoggerExtension : SoapExtension
{
    private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
    private Stream oldStream;
    private Stream newStream;

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }

    public override object GetInitializer(Type serviceType)
    {
        return null;
    }

    public override void Initialize(object initializer)
    {

    }

    public override System.IO.Stream ChainStream(System.IO.Stream stream)
    {
        oldStream = stream;
        newStream = new MemoryStream();
        return newStream;
    }

    public override void ProcessMessage(SoapMessage message)
    {

        switch (message.Stage)
        {
            case SoapMessageStage.BeforeSerialize:
                break;
            case SoapMessageStage.AfterSerialize:
                Log(message, "AfterSerialize");
                    CopyStream(newStream, oldStream);
                    newStream.Position = 0;
                break;
                case SoapMessageStage.BeforeDeserialize:
                    CopyStream(oldStream, newStream);
                    Log(message, "BeforeDeserialize");
                break;
            case SoapMessageStage.AfterDeserialize:
                break;
        }
    }

    public void Log(SoapMessage message, string stage)
    {

        newStream.Position = 0;
        string contents = (message is SoapServerMessage) ? "SoapRequest " : "SoapResponse ";
        contents += stage + ";";

        StreamReader reader = new StreamReader(newStream);

        contents += reader.ReadToEnd();

        newStream.Position = 0;

        log.Debug(contents);
    }

    void ReturnStream()
    {
        CopyAndReverse(newStream, oldStream);
    }

    void ReceiveStream()
    {
        CopyAndReverse(newStream, oldStream);
    }

    public void ReverseIncomingStream()
    {
        ReverseStream(newStream);
    }

    public void ReverseOutgoingStream()
    {
        ReverseStream(newStream);
    }

    public void ReverseStream(Stream stream)
    {
        TextReader tr = new StreamReader(stream);
        string str = tr.ReadToEnd();
        char[] data = str.ToCharArray();
        Array.Reverse(data);
        string strReversed = new string(data);

        TextWriter tw = new StreamWriter(stream);
        stream.Position = 0;
        tw.Write(strReversed);
        tw.Flush();
    }
    void CopyAndReverse(Stream from, Stream to)
    {
        TextReader tr = new StreamReader(from);
        TextWriter tw = new StreamWriter(to);

        string str = tr.ReadToEnd();
        char[] data = str.ToCharArray();
        Array.Reverse(data);
        string strReversed = new string(data);
        tw.Write(strReversed);
        tw.Flush();
    }

    private void CopyStream(Stream fromStream, Stream toStream)
    {
        try
        {
            StreamReader sr = new StreamReader(fromStream);
            StreamWriter sw = new StreamWriter(toStream);
            sw.WriteLine(sr.ReadToEnd());
            sw.Flush();
        }
        catch (Exception ex)
        {
            string message = String.Format("CopyStream failed because: {0}", ex.Message);
            log.Error(message, ex);
        }
    }
}

[AttributeUsage(AttributeTargets.Method)]
public class SoapLoggerExtensionAttribute : SoapExtensionAttribute
{
    private int priority = 1; 

    public override int Priority
    {
        get { return priority; }
        set { priority = value; }
    }

    public override System.Type ExtensionType
    {
        get { return typeof (SoapLoggerExtension); }
    }
}

Anschließend fügen Sie Ihrer web.config den folgenden Abschnitt hinzu, in dem YourNamespace und YourAssembly auf die Klasse und Assembly Ihrer SoapExtension verweisen:

<webServices>
  <soapExtensionTypes>
    <add type="YourNamespace.SoapLoggerExtension, YourAssembly" 
       priority="1" group="0" />
  </soapExtensionTypes>
</webServices>
John Lemp
quelle
Ich werde es versuchen. Ich hatte mich mit Seifenverlängerungen befasst, aber es sah für mich eher nach dem Hosting des Dienstes aus. Wird Ihnen das Häkchen geben, wenn es funktioniert :)
Andrew Harry
Ja, dies funktioniert, um die von Ihrer Webanwendung über den generierten Proxy getätigten Anrufe zu protokollieren.
John Lemp
Dies ist der Weg, den ich eingeschlagen habe. Er funktioniert sehr gut und ich kann xpath verwenden, um vertrauliche Daten zu maskieren, bevor ich sie protokolliere.
Dave Baghdanov
Ich musste der Seifenanforderung auf dem Client Header hinzufügen. Das hat mir geholfen. rhyous.com/2015/04/29/…
Rhyous
Ich bin auch neu in c # und bin mir nicht sicher, was ich für den Namen der Versammlung verwenden soll. Die Erweiterung scheint nicht ausgeführt zu werden.
Collin Anderson
28

Ich bin mir nicht sicher, warum ich so viel Aufhebens um web.config oder eine Serializer-Klasse mache. Der folgende Code hat bei mir funktioniert:

XmlSerializer xmlSerializer = new XmlSerializer(myEnvelope.GetType());

using (StringWriter textWriter = new StringWriter())
{
    xmlSerializer.Serialize(textWriter, myEnvelope);
    return textWriter.ToString();
}
Bimmergebunden
quelle
17
Woher kommt myEnvelopedas?
ProfK
6
Ich verstehe nicht, warum diese Antwort nicht mehr positive Stimmen hat, direkt und auf den Punkt! Gut gemacht!
Batista
1
myEnvalope ist das Objekt, das Sie über den Webdienst senden. Ich konnte dies nicht wie beschrieben zum Laufen bringen (insbesondere die Funktion textWriter.tostring), aber es funktionierte für meine Zwecke im Debug-Modus gut genug, da Sie die unformatierte XML nach dem Aufruf von serialize sehen können. Ich schätze diese Antwort sehr, da einige der anderen mit gemischten Ergebnissen gearbeitet haben (die Protokollierung mit der Datei web.config gab beispielsweise nur einen Teil der XML-Datei zurück und war auch nicht sehr einfach zu analysieren).
user2366842
@Bimmerbound: Würde dies mit sicheren Seifennachrichten funktionieren, die über ein verschlüsseltes VPN gesendet werden?
Unser Mann in Bananen
6
Diese Antwort erzeugt keinen Seifenumschlag, sondern serialisiert nur in XML. Wie bekomme ich eine Anfrage für einen Seifenumschlag?
Ali Karaca
21

Probieren Sie Fiddler2 aus, damit Sie die Anforderungen und Antworten überprüfen können. Es könnte erwähnenswert sein, dass Fiddler sowohl mit http- als auch mit https-Verkehr arbeitet.

Aaron Fischer
quelle
Es ist ein https-verschlüsselter Dienst
Andrew Harry
8
Fiddler kann den https-Verkehr entschlüsseln
Aaron Fischer
1
Ich fand diese Lösung besser als das Hinzufügen von Ablaufverfolgung zur web / app.config. Vielen Dank!
Andyuk
1
Für die Verwendung von system.diagnostics in der Konfigurationsdatei muss jedoch keine Drittanbieter-App installiert werden
Azat
1
@AaronFischer: Würde Fiddler mit Seifennachrichten arbeiten, die über ein verschlüsseltes VPN gesendet werden?
Unser Mann in Bananen
6

Es sieht so aus, als ob die Lösung von Tim Carter nicht funktioniert, wenn der Aufruf der Webreferenz eine Ausnahme auslöst. Ich habe versucht, auf die unformatierte Webantwort zuzugreifen, damit ich sie (im Code) im Fehlerhandler untersuchen kann, sobald die Ausnahme ausgelöst wird. Ich stelle jedoch fest, dass das von Tims Methode geschriebene Antwortprotokoll leer ist, wenn der Aufruf eine Ausnahme auslöst. Ich verstehe den Code nicht vollständig, aber es scheint, dass Tims Methode in den Prozess einschneidet, nachdem .Net die Webantwort bereits ungültig gemacht und verworfen hat.

Ich arbeite mit einem Client zusammen, der einen Webdienst manuell mit niedriger Codierung entwickelt. Zu diesem Zeitpunkt fügen sie der Antwort VOR der SOAP-formatierten Antwort ihre eigenen internen Prozessfehlermeldungen als HTML-formatierte Nachrichten hinzu. Natürlich sprengt die automagische .Net-Webreferenz dies. Wenn ich nach dem Auslösen einer Ausnahme auf die unformatierte HTTP-Antwort zugreifen könnte, könnte ich jede SOAP-Antwort in der gemischten HTTP-Antwort suchen und analysieren und wissen, ob meine Daten in Ordnung sind oder nicht.

Später ...

Hier ist eine Lösung, die auch nach einer Ausführung funktioniert (beachten Sie, dass ich erst nach der Antwort bin - könnte auch die Anfrage erhalten):

namespace ChuckBevitt
{
    class GetRawResponseSoapExtension : SoapExtension
    {
        //must override these three methods
        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return null;
        }
        public override object GetInitializer(Type serviceType)
        {
            return null;
        }
        public override void Initialize(object initializer)
        {
        }

        private bool IsResponse = false;

        public override void ProcessMessage(SoapMessage message)
        {
            //Note that ProcessMessage gets called AFTER ChainStream.
            //That's why I'm looking for AfterSerialize, rather than BeforeDeserialize
            if (message.Stage == SoapMessageStage.AfterSerialize)
                IsResponse = true;
            else
                IsResponse = false;
        }

        public override Stream ChainStream(Stream stream)
        {
            if (IsResponse)
            {
                StreamReader sr = new StreamReader(stream);
                string response = sr.ReadToEnd();
                sr.Close();
                sr.Dispose();

                File.WriteAllText(@"C:\test.txt", response);

                byte[] ResponseBytes = Encoding.ASCII.GetBytes(response);
                MemoryStream ms = new MemoryStream(ResponseBytes);
                return ms;

            }
            else
                return stream;
        }
    }
}

So konfigurieren Sie es in der Konfigurationsdatei:

<configuration>
     ...
  <system.web>
    <webServices>
      <soapExtensionTypes>
        <add type="ChuckBevitt.GetRawResponseSoapExtension, TestCallWebService"
           priority="1" group="0" />
      </soapExtensionTypes>
    </webServices>
  </system.web>
</configuration>

"TestCallWebService" sollte durch den Namen der Bibliothek ersetzt werden (dies war zufällig der Name der Testkonsolen-App, in der ich gearbeitet habe).

Sie sollten wirklich nicht zu ChainStream gehen müssen; Sie sollten dies in ProcessMessage einfacher tun können als:

public override void ProcessMessage(SoapMessage message)
{
    if (message.Stage == SoapMessageStage.BeforeDeserialize)
    {
        StreamReader sr = new StreamReader(message.Stream);
        File.WriteAllText(@"C:\test.txt", sr.ReadToEnd());
        message.Stream.Position = 0; //Will blow up 'cause type of stream ("ConnectStream") doesn't alow seek so can't reset position
    }
}

Wenn Sie nach SoapMessage.Stream suchen, sollte es sich um einen schreibgeschützten Stream handeln, mit dem Sie die Daten an dieser Stelle überprüfen können. Dies ist ein Fehler, denn wenn Sie den Stream lesen, nachfolgende Verarbeitungsbomben ohne Datenfehler gefunden haben (Stream war am Ende) und Sie die Position nicht auf den Anfang zurücksetzen können.

Interessanterweise funktioniert die ProcessMessage-Methode, wenn Sie beide Methoden ausführen, ChainStream und ProcessMessage, da Sie den Stream-Typ in ChainStream von ConnectStream in MemoryStream geändert haben und MemoryStream Suchvorgänge zulässt. (Ich habe versucht, den ConnectStream in MemoryStream zu übertragen - war nicht zulässig.)

Also ..... Microsoft sollte entweder Suchvorgänge für den ChainStream-Typ zulassen oder SoapMessage.Stream wirklich zu einer schreibgeschützten Kopie machen, wie sie sein soll. (Schreiben Sie Ihren Kongressabgeordneten usw.)

Ein weiterer Punkt. Nachdem ich eine Möglichkeit erstellt hatte, die unformatierte HTTP-Antwort nach einer Ausnahme abzurufen, erhielt ich immer noch nicht die vollständige Antwort (wie von einem HTTP-Sniffer ermittelt). Dies lag daran, dass der Entwicklungswebdienst beim Hinzufügen der HTML-Fehlermeldungen am Anfang der Antwort den Content-Length-Header nicht anpasste, sodass der Content-Length-Wert kleiner als die Größe des tatsächlichen Antwortkörpers war. Alles, was ich bekam, war die Anzahl der Zeichen für den Wert der Inhaltslänge - der Rest fehlte. Wenn .Net den Antwortstrom liest, liest es natürlich nur die Anzahl der Zeichen für die Inhaltslänge und lässt nicht zu, dass der Wert für die Inhaltslänge möglicherweise falsch ist. Das ist so wie es sein sollte; Wenn der Content-Length-Header-Wert jedoch falsch ist, erhalten Sie den gesamten Antworttext nur mit einem HTTP-Sniffer (ich benutze HTTP Analyzer vonhttp://www.ieinspector.com ).

Chuck Bevitt
quelle
2

Ich würde es vorziehen, wenn das Framework die Protokollierung für Sie durchführt, indem es einen Protokollierungsdatenstrom einbindet, der protokolliert, während das Framework den zugrunde liegenden Datenstrom verarbeitet. Das Folgende ist nicht so sauber, wie ich es gerne hätte, da Sie sich in der ChainStream-Methode nicht zwischen Anfrage und Antwort entscheiden können. Das Folgende ist, wie ich damit umgehe. Vielen Dank an Jon Hanna für das Überschreiben einer Stream-Idee

public class LoggerSoapExtension : SoapExtension
{
    private static readonly string LOG_DIRECTORY = ConfigurationManager.AppSettings["LOG_DIRECTORY"];
    private LogStream _logger;

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }
    public override object GetInitializer(Type serviceType)
    {
        return null;
    }
    public override void Initialize(object initializer)
    {
    }
    public override System.IO.Stream ChainStream(System.IO.Stream stream)
    {
        _logger = new LogStream(stream);
        return _logger;
    }
    public override void ProcessMessage(SoapMessage message)
    {
        if (LOG_DIRECTORY != null)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    _logger.Type = "request";
                    break;
                case SoapMessageStage.AfterSerialize:
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    _logger.Type = "response";
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
            }
        }
    }
    internal class LogStream : Stream
    {
        private Stream _source;
        private Stream _log;
        private bool _logSetup;
        private string _type;

        public LogStream(Stream source)
        {
            _source = source;
        }
        internal string Type
        {
            set { _type = value; }
        }
        private Stream Logger
        {
            get
            {
                if (!_logSetup)
                {
                    if (LOG_DIRECTORY != null)
                    {
                        try
                        {
                            DateTime now = DateTime.Now;
                            string folder = LOG_DIRECTORY + now.ToString("yyyyMMdd");
                            string subfolder = folder + "\\" + now.ToString("HH");
                            string client = System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Request != null && System.Web.HttpContext.Current.Request.UserHostAddress != null ? System.Web.HttpContext.Current.Request.UserHostAddress : string.Empty;
                            string ticks = now.ToString("yyyyMMdd'T'HHmmss.fffffff");
                            if (!Directory.Exists(folder))
                                Directory.CreateDirectory(folder);
                            if (!Directory.Exists(subfolder))
                                Directory.CreateDirectory(subfolder);
                            _log = new FileStream(new System.Text.StringBuilder(subfolder).Append('\\').Append(client).Append('_').Append(ticks).Append('_').Append(_type).Append(".xml").ToString(), FileMode.Create);
                        }
                        catch
                        {
                            _log = null;
                        }
                    }
                    _logSetup = true;
                }
                return _log;
            }
        }
        public override bool CanRead
        {
            get
            {
                return _source.CanRead;
            }
        }
        public override bool CanSeek
        {
            get
            {
                return _source.CanSeek;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return _source.CanWrite;
            }
        }

        public override long Length
        {
            get
            {
                return _source.Length;
            }
        }

        public override long Position
        {
            get
            {
                return _source.Position;
            }
            set
            {
                _source.Position = value;
            }
        }

        public override void Flush()
        {
            _source.Flush();
            if (Logger != null)
                Logger.Flush();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _source.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            _source.SetLength(value);
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            count = _source.Read(buffer, offset, count);
            if (Logger != null)
                Logger.Write(buffer, offset, count);
            return count;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            _source.Write(buffer, offset, count);
            if (Logger != null)
                Logger.Write(buffer, offset, count);
        }
        public override int ReadByte()
        {
            int ret = _source.ReadByte();
            if (ret != -1 && Logger != null)
                Logger.WriteByte((byte)ret);
            return ret;
        }
        public override void Close()
        {
            _source.Close();
            if (Logger != null)
                Logger.Close();
            base.Close();
        }
        public override int ReadTimeout
        {
            get { return _source.ReadTimeout; }
            set { _source.ReadTimeout = value; }
        }
        public override int WriteTimeout
        {
            get { return _source.WriteTimeout; }
            set { _source.WriteTimeout = value; }
        }
    }
}
[AttributeUsage(AttributeTargets.Method)]
public class LoggerSoapExtensionAttribute : SoapExtensionAttribute
{
    private int priority = 1;
    public override int Priority
    {
        get
        {
            return priority;
        }
        set
        {
            priority = value;
        }
    }
    public override System.Type ExtensionType
    {
        get
        {
            return typeof(LoggerSoapExtension);
        }
    }
}

quelle
1
@ TimCarter es hat bei mir funktioniert, das einzige, was ich gelöscht habe, ist der Client-Teil, der mir einen Namen mit einem Doppelpunkt gibt, was eine Ausnahme verursacht. Sobald ich diese Zeile lösche, funktioniert sie gut für diejenigen, die eine Datei für jede Anfrage / Antwort haben möchten. Das einzige, was Sie hinzufügen müssen, ist etwas Offensichtliches, wenn Sie die anderen Antworten lesen, und Sie müssen den Knoten web.config hinzufügen, um die soapExtension anzugeben. Vielen Dank!
Netadictos
1

Hier ist eine vereinfachte Version der Top-Antwort. Fügen Sie dies dem <configuration>Element Ihrer web.configoder App.configDatei hinzu. Es wird eine trace.logDatei im bin/DebugOrdner Ihres Projekts erstellt . Oder Sie können mithilfe des initializeDataAttributs einen absoluten Pfad für die Protokolldatei angeben .

  <system.diagnostics>
    <trace autoflush="true"/>
    <sources>
      <source name="System.Net" maxdatasize="9999" tracemode="protocolonly">
        <listeners>
          <add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="trace.log"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="System.Net" value="Verbose"/>
    </switches>
  </system.diagnostics>

Es warnt davor, dass die Attribute maxdatasizeund tracemodenicht zulässig sind, erhöht jedoch die Datenmenge, die protokolliert werden kann, und vermeidet, alles in hexadezimaler Form zu protokollieren.

Collin Anderson
quelle
0

Sie haben nicht angegeben, welche Sprache Sie verwenden, aber unter der Annahme von C # / .NET können Sie SOAP-Erweiterungen verwenden .

Verwenden Sie andernfalls einen Schnüffler wie Wireshark

rbrayb
quelle
1
Ja, wireshark ausprobiert, es kann mir nur die Header-Informationen anzeigen, der Seifeninhalt ist verschlüsselt.
Andrew Harry
Das liegt vielleicht daran, dass Sie https verwenden. Soweit ich mich erinnern kann, arbeiten SOAP-Erweiterungen auf einer höheren Ebene, sodass Sie die Daten nach der Entschlüsselung sehen können sollten.
Rbrayb
2
Verwenden Sie Fiddler anstelle von Wireshark, es entschlüsselt https sofort.
Rodrigo Strauss
-1

Mir ist klar, dass ich ziemlich spät zur Party komme, und da die Sprache nicht wirklich angegeben wurde, ist hier eine VB.NET-Lösung, die auf der Antwort von Bimmerbound basiert, falls jemand darüber stolpert und eine Lösung benötigt. Hinweis: Wenn Sie dies noch nicht getan haben, benötigen Sie einen Verweis auf die Stringbuilder-Klasse in Ihrem Projekt.

 Shared Function returnSerializedXML(ByVal obj As Object) As String
    Dim xmlSerializer As New System.Xml.Serialization.XmlSerializer(obj.GetType())
    Dim xmlSb As New StringBuilder
    Using textWriter As New IO.StringWriter(xmlSb)
        xmlSerializer.Serialize(textWriter, obj)
    End Using


    returnSerializedXML = xmlSb.ToString().Replace(vbCrLf, "")

End Function

Rufen Sie einfach die Funktion auf und es wird eine Zeichenfolge mit der serialisierten XML des Objekts zurückgegeben, das Sie an den Webdienst übergeben möchten (realistisch gesehen sollte dies für jedes Objekt funktionieren, das Sie auch darauf werfen möchten).

Als Randnotiz besteht der Ersetzungsaufruf in der Funktion vor der Rückgabe der XML darin, vbCrLf-Zeichen aus der Ausgabe zu entfernen. Meins hatte eine Menge davon in der generierten XML-Datei, dies hängt jedoch offensichtlich davon ab, was Sie serialisieren möchten, und ich denke, dass sie möglicherweise entfernt werden, während das Objekt an den Webdienst gesendet wird.

user2366842
quelle
Wer mich mit der Gegenstimme geschlagen hat, kann mir gerne mitteilen, was mir fehlt / was verbessert werden kann.
user2366842