XmlDictionaryReader可以真正处理二进制XML吗?如果不是,那该怎么办?

时间:2009-08-18 04:56:32

标签: xml wcf soap binary

我正在尝试编写一个debugging tool,允许用户以纯文本格式查看WCF的新二进制XML格式(application / soap + msbin1)。一旦我找到XmlDictionaryReader课程,我认为我会在几分钟内完成,但它没有按预期工作。

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

我在网上找到或自己生成的每个“soap + msbin1”样本都会在 doc.Load()中抛出一个解析异常。

要查看发生了什么,我创建了一个简单的测试应用程序并从另一个方向攻击了该问题。

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

运行它会启动一个看起来像这样的http请求:

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

我以两种不同的方式将此XML 转换为二进制文件。首先,使用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()

然后,使用WCF和相同的网络嗅探器:

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

方法#1给出了397字节的二进制外观块。方法#2显示了169字节的非常不同的二进制块。除了两个输出中出现的一些字符串之外,我在两种编码中看不到很多相似之处。难怪,XmlDictionaryReader无法理解WCF服务的输出!

解码这种格式是否有秘密,或者我完全走错了路?

4 个答案:

答案 0 :(得分:4)

Carlos Figueira @ MS获得了很好的回应。

  

WCF使用“静态字典”将一些众所周知的字符串编码为(小)ID。例如,字符串“Envelope”,“http://www.w3.org/2003/05/soap-envelope”,“http://www.w3.org/2005/08/addressing”等仅表示为几个字节。因此,为了能够解析WCF发送的请求,您需要将该字典(IXmlDictionary)传递给XmlDictionaryReader.CreateBinaryReader方法。

     

整个词典记录在http://msdn.microsoft.com/en-us/library/cc219175(PROT.10).aspx。读取请求的代码应如下所示:

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

如果能够找到有效的解决方案,我会用更多细节更新这个答案。

编辑:烨,就像一个魅力! Carlos解决方案的唯一问题是ReadOuterXml()似乎不起作用。读入XmlDocument然后写出一个Stream可以更好地控制格式化,所以这就是我所坚持的。

注意:在MS规范中复制字典大约需要500行代码。我建议复制我的,除非你是一个受虐狂 - http://tfstoys.codeplex.com/sourcecontrol/changeset/view/26191?projectName=tfstoys#499486

答案 1 :(得分:1)

Binary gunk .....嗯,你正在使用BinaryEncoding!

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

你可以 - 只是出于争论的缘故 - 尝试使用TextEncoding,看看是否有效?另外 - 默认情况下,WCF会对每条消息进行加密和签名,如果您捕获线路,应该只能看到二进制垃圾! :-)

此外,您在WCF通信中的哪一点尝试拦截这些消息?

如果您在客户端和服务器之间“在线上”拦截它们,它们将在您的设置中进行二进制编码 - 您将获得gooblydeguck。

但是,WCF提供了一个很好的可扩展性故事,因此您可以在之前捕获消息它们是二进制编码的(在客户端上),或之后它们已经被捕获解码(在服务器上,传入)。检查消息检查器 - 它们允许您查看通过WCF堆栈传输的消息,因为它们是在客户端上构建的并在服务器上解压缩!

查看一些优秀的资源:

马克

答案 2 :(得分:1)

目前正在努力解决这个问题,但我通过使用反射来获取ServiceModel程序集中的静态字典,从而为Dictionary构造提供了一个更短的解决方法:

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

答案 3 :(得分:0)

除了marc_s给出的答案之外,请记住,XmlDictionaryReader只是一个扩展XmlReader接口的抽象类(同样适用于XmlDictionaryWriter)。它们仍然纯粹根据InfoSet进行处理,而不是任何具体的表示。

在实际读取/写入BinaryMessageEncoder使用的二进制xml格式方面,这是由WCF实现的两个内部类完成的:XmlBinaryReader和XmlBinaryWriter。我猜你可以直接使用它们,如果你可以使用一些反射,但除此之外,它们实际上是通过BinaryMessageEncoder间接使用。

顺便说一句,您可以直接使用编码器,因为我在this blog post中显示。