3 Stimmen

Kann XmlDictionaryReader wirklich binäres XML verarbeiten? Wenn nicht, was dann?

Ich versuche, ein Buch zu schreiben. Fehlersuchwerkzeug die es dem Benutzer ermöglicht, das neue binäre XML-Format von WCF (application/soap +msbin1) in Klartext anzuzeigen. Sobald ich die XmlDictionaryReader Klasse Ich dachte, ich wäre in wenigen Minuten fertig, aber es funktioniert nicht wie erwartet.

private string DecodeBinaryXML(byte[] binaryBuffer)
{
    if (binaryBuffer == null)
    {
        return "";
    }

    try
    {
        var doc = new XmlDocument();
        using (var binaryReader = XmlDictionaryReader.CreateBinaryReader(binaryBuffer, XmlDictionaryReaderQuotas.Max))
        {                    
            doc.Load(binaryReader);
            binaryReader.Close();
        }

        var textBuffer = new StringBuilder();
        var settings = new XmlWriterSettings()
        {
            // lots of code not relevant to the question
        };
        using (var writer = XmlWriter.Create(textBuffer, settings))
        {
            doc.Save(writer);
            writer.Close();
        }

        return textBuffer.ToString();
    }
    catch (Exception ex)
    {
        // just display errors in the text viewer
        return ex.ToString();
    }
}

Jedes einzelne "soap+msbin1"-Beispiel, das ich online gefunden oder selbst erstellt habe, löst eine Parse-Exception aus bei doc.Load() .

Um zu sehen, was los war, habe ich eine einfache Testanwendung erstellt und das Problem aus der anderen Richtung angegangen.

// client
static void Main(string[] args)
{
    var binding = new CustomBinding(new TextMessageEncodingBindingElement(), 
                                    new HttpTransportBindingElement());            
    var proxy = ChannelFactory<IService1>.CreateChannel(binding, 
               new EndpointAddress("http://ipv4.fiddler:25381/Service1.svc"));
    Console.WriteLine(proxy.Echo("asdf"));
}

// shared interface
[ServiceContract()]
public interface IService1
{
    [OperationContract]
    string Echo(string input);
}

// server
public class Service1 : IService1
{
    public string Echo(string input)
    {
        return "WCF says hi to: " + input;
    }
}

Die Ausführung löst eine http-Anfrage aus, die wie folgt aussieht:

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" 
            xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
     <a:Action s:mustUnderstand="1">http://tempuri.org/IService1/Echo</a:Action>
     <a:MessageID>urn:uuid:21a33e81-bfab-424f-a2e5-5116101a7319</a:MessageID>
     <a:ReplyTo>
        <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
     </a:ReplyTo>
     <a:To s:mustUnderstand="1">http://ipv4.fiddler:25381/Service1.svc</a:To>
  </s:Header>

  <s:Body>
      <Echo xmlns="http://tempuri.org/">
          <input>asdf</input>
      </Echo>
  </s:Body>
</s:Envelope>

Ich habe dieses XML konvertiert in binär auf zwei verschiedene Arten. Erstens, mit XmlDictionaryWriter:

$fs = [system.io.file]::Create("c:\temp\soap.bin")
$writer = [system.xml.xmldictionarywriter]::CreateBinaryWriter($fs)
$xml = [xml] (gc C:\temp\soap.xml)
$xml.Save($writer)
$writer.Close(); $fs.Close()

Dann, mit WCF und demselben Netzwerk-Sniffer:

    @@ -1,7 +1,7 @@
     // client
     static void Main(string[] args)
     {
-        var binding = new CustomBinding(new TextMessageEncodingBindingElement(), 
+        var binding = new CustomBinding(new BinaryMessageEncodingBindingElement(), 
                                         new HttpTransportBindingElement()); 

Methode Nr. 1 ergab 397 Byte binär aussehenden Datenmüll. Methode #2 zeigt 169 Bytes sehr unterschiedlichen binären Müll. Abgesehen von einigen Zeichenfolgen, die in beiden Ausgaben vorkommen, sehe ich keine große Ähnlichkeit zwischen den beiden Kodierungen. Kein Wunder also, dass XmlDictionaryReader die Ausgabe eines WCF-Dienstes nicht verstehen kann!

Gibt es ein Geheimnis für die Entschlüsselung dieses Formats, oder bin ich auf dem völlig falschen Weg?

4voto

Richard Berg Punkte 20483

Ich habe eine vielversprechende Antwort von Carlos Figueira @ MS erhalten.

WCF verwendet ein "statisches Wörterbuch", das einige bekannte Zeichenketten in (kleine) IDs kodiert. Zum Beispiel sind die Zeichenketten "Envelope", " http://www.w3.org/2003/05/soap-envelope ", " http://www.w3.org/2005/08/addressing " und so weiter werden nur als wenige Bytes dargestellt. Um die von WCF gesendeten Anfragen analysieren zu können, müssen Sie dieses Wörterbuch (IXmlDictionary) an die Methode XmlDictionaryReader.CreateBinaryReader übergeben.

Das gesamte Wörterbuch ist dokumentiert unter http://msdn.microsoft.com/en-us/library/cc219175(PROT.10).aspx . Der Code zum Lesen der Anfrage sollte etwa so aussehen:

public class Post_e9208540_7877_4318_909d_92eb8490ab58
{
    static XmlDictionary dictionary;
    static XmlDictionary GetDictionary()
    {
        if (dictionary == null)
        {
            XmlDictionary temp = new XmlDictionary();
            dictionary = temp;
            temp.Add("mustUnderstand");
            temp.Add("Envelope");
            temp.Add("http://www.w3.org/2003/05/soap-envelope");
            temp.Add("http://www.w3.org/2005/08/addressing");
            ...
        }
        return dictionary;
    }
    public static void DecodeBinaryMessage(byte[] message)
    {
        XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(message, 0, message.Length, GetDictionary(), XmlDictionaryReaderQuotas.Max);
        Console.WriteLine(reader.ReadOuterXml());
    }
} 

Ich werde diese Antwort mit weiteren Details aktualisieren, falls sie zu einer funktionierenden Lösung führt.

edit: ja, funktioniert wie ein Zauber! Das einzige Problem mit Carlos' Lösung ist, dass ReadOuterXml() nicht zu funktionieren scheint. Lesen in ein XmlDocument und dann schreiben aus einem Stream ermöglicht viel bessere Kontrolle über die Formatierung sowieso, so dass ist das, was ich mit stecken.

Hinweis: Die Replikation des Wörterbuchs in der MS-Spezifikation erfordert etwa 500 Zeilen Code. Ich würde empfehlen, meinen Code zu kopieren, es sei denn, Sie sind ein Masochist - http://tfstoys.codeplex.com/sourcecontrol/changeset/view/26191?projectName=tfstoys#499486

1voto

marc_s Punkte 701497

Binary gunk..... nun, Sie verwenden die BinaryEncoding!

var binding = new CustomBinding(new BinaryMessageEncodingBindingElement(), 
                                new HttpTransportBindingElement());   

Können Sie - nur um des Argumentes willen - versuchen, stattdessen TextEncoding zu verwenden, und sehen, ob das funktioniert? Außerdem wird WCF standardmäßig jede Nachricht verschlüsseln und signieren, so dass Sie, wenn Sie die Leitung abfangen, die sollte nur binären Müll sehen! :-)

An welchem Punkt der WCF-Kommunikation versuchen Sie außerdem, diese Nachrichten abzufangen?

Wenn Sie sie "auf dem Draht" zwischen dem Client und dem Server abfangen, werden sie in Ihrer Einrichtung binär kodiert - und Sie bekommen Gooblydeguck.

WCF bietet jedoch eine große Erweiterungsmöglichkeit, so dass Sie die Nachrichten erfassen können vor sie sind binär kodiert (auf dem Client), oder nach sie entschlüsselt wurden (auf dem Server, eingehend). Prüfen Sie in Nachricht Inspektoren - können Sie einen Blick auf die Nachrichten werfen, die den WCF-Stack durchlaufen, während sie auf dem Client erstellt und auf dem Server entpackt werden!

Hier finden Sie einige ausgezeichnete Ressourcen:

Marc

1voto

Mark Rendle Punkte 8989

Kämpfen mit diesem im Moment, aber ich habe mit einer kürzeren Umgehung der Dictionary-Konstruktion kommen, durch die Verwendung von Reflexion auf das statische Wörterbuch in der ServiceModel Assembly zu erhalten:

var serviceModelAssembly = Assembly.GetAssembly(typeof (System.ServiceModel.ActionNotSupportedException));
var serviceModelDictionaryType = serviceModelAssembly.GetTypes().Single(t => t.Name.Equals("ServiceModelDictionary"));
var currentVersionProperty = serviceModelDictionaryType.GetProperty("CurrentVersion");
var serviceModelDictionary = (IXmlDictionary)currentVersionProperty.GetValue(null, null);
// Now use serviceModelDictionary as argument for reader

0voto

tomasr Punkte 13533

Neben der Antwort von marc_s ist zu beachten, dass XmlDictionaryReader nur eine abstrakte Klasse ist, die die Schnittstelle für XmlReader erweitert (dasselbe gilt für XmlDictionaryWriter). Es handelt sich immer noch um das InfoSet und nicht um eine spezifische Darstellung desselben.

In Bezug auf das Lesen/Schreiben des binären Xml-Formats, das vom BinaryMessageEncoder verwendet wird, wird dies von zwei internen Klassen erledigt, die von WCF implementiert werden: XmlBinaryReader und XmlBinaryWriter. Ich schätze, man könnte sie direkt verwenden, wenn man etwas Reflection verwenden kann, aber ansonsten sind sie wirklich dazu gedacht, indirekt über den BinaryMessageEncoder verwendet zu werden.

Übrigens können Sie den Encoder auch direkt verwenden, wie ich in dieser Blogbeitrag .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X