代码之家  ›  专栏  ›  技术社区  ›  Richard Berg

XmlDictionaryReader真的能处理二进制XML吗?如果没有,怎么办?

  •  3
  • Richard Berg  · 技术社区  · 15 年前

    我想写一篇 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”示例都会在处引发分析异常 加载() .

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

    // 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 回复  |  直到 14 年前
        1
  •  4
  •   Richard Berg    15 年前

    从Carlos Figueira@ms得到了一个有希望的回复。

    WCF使用“静态字典”,将一些已知字符串编码为(小)ID。例如,字符串“envelope”, http://www.w3.org/2003/05/soap-envelope “,” http://www.w3.org/2005/08/addressing “等等只表示为几个字节。为了能够解析WCF发送的请求,需要将字典(IXML字典)传递给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());
        }
    } 
    

    如果这会导致一个有效的解决方案,我会用更多的细节更新这个答案。

    编辑:是的,工作起来很有魅力!卡洛斯解决方案的唯一问题是readouterxml()似乎不起作用。读取一个XML文档,然后写出一个流,可以更好地控制格式,所以这就是我一直坚持的观点。

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

        2
  •  1
  •   marc_s Franci    15 年前

    二元火药……你用的是二进制编码!

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

    你能不能——只是为了参数的缘故——试着用文本编码来代替,看看它是否有效??另外-在默认情况下,WCF将加密并签署每个消息,如果您捕获了该线,您将 应该 只看到二进制垃圾!-)

    另外,在WCF通信中,您试图在什么时候拦截这些消息?

    如果你在客户机和服务器之间“在线”截获它们,它们将在你的设置中被二进制编码——你将得到gooblydeguck。

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

    查看一些优秀资源:

    马克

        3
  •  1
  •   Mark Rendle    14 年前

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

    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
    
        4
  •  0
  •   tomasr    15 年前

    除了Marc_给出的答案之外,请记住,xmlDictionaryReader只是一个扩展xmlReader接口的抽象类(同样适用于xmlDictionaryWriter)。他们仍然仅仅是在处理信息集,而不是它的任何具体表示。

    就实际读/写BinaryMessageEncoder使用的二进制XML格式而言,这是由两个由wcf实现的内部类完成的:xmlBinaryReader和xmlBinaryWriter。我想,如果可以在周围使用一些反射,您可以直接使用它们,但除此之外,它们实际上是通过BinaryMessageEncoder间接使用的。

    顺便说一句,您当然可以直接使用编码器,正如我在 this blog post .