【问题标题】:Can XmlDictionaryReader really handle binary XML? If not, what does?XmlDictionaryReader 真的可以处理二进制 XML 吗?如果不是,那是什么?
【发布时间】:2009-08-18 04:56:32
【问题描述】:

我正在尝试编写一个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 服务的输出!

解码这种格式有什么秘诀,还是我完全走错了路?

【问题讨论】:

    标签: xml wcf soap binary


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      Binary gunk.....好吧,您使用的是 BinaryEncoding!

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

      你能不能 - 只是为了争论 - 尝试使用 TextEncoding 来代替,看看它是否有效?此外 - 默认情况下,WCF 将对每条消息进行加密和签名,如果您陷于线路,您应该只能看到二进制垃圾! :-)

      另外,您试图在 WCF 通信的哪个时间点拦截这些消息?

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

      但是,WCF 提供了出色的可扩展性,因此您可以捕获消息它们被二进制编码(在客户端上)或它们被编码之后解码(在服务器上,传入)。检查 Message Inspectors - 它们允许您查看在 WCF 堆栈中传输的消息,因为它们是在客户端上构建并在服务器上解包的!

      查看一些优秀的资源:

      马克

      【讨论】:

      • “Gunk”是比喻性的——我当然使用了十六进制编辑器 :) 我正在“在线”拦截它们(嗯,在 http 代理上)。期望它是二进制的。我没想到的是 XmlDictionary* 类使用与 WCF 类不同的二进制编码;我以为一个是建立在另一个之上的。我会调查这些链接,但我真的希望有一个工具可以“在线”工作而无需客户端或服务器干预。
      • 好吧,正如我所说 - 默认情况下(除非您自己专门将其关闭),WCF 线路上的数据已加密和签名 - 不确定您是否可以轻松解决这个问题 :-(
      • 真的吗?最广泛使用的绑定(basicHttpBinding)绝对不加密;我一直在嗅探它,因此希望重用那些嗅探器工具。我 90% 确定我上面的代码定义的绑定也没有打开加密。如果您有这方面的文档,我很乐意看到它!
      【解决方案3】:

      目前正在为此苦苦挣扎,但我想出了一个更短的字典构造解决方法,方法是使用反射来获取 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】:

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

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

        顺便说一句,你当然可以直接使用编码器,就像我在this blog post 中展示的那样。

        【讨论】:

        • 我正在使用 XmlBinaryReader/Writer 类。让我更详细地澄清这个问题。
        • 这篇博文很有帮助。如您所描述的那样使用编码器可以使输出更接近“方法#2”。有对应的解码器吗?我能找到的最接近的东西是 mebe.GetProperty。该方法有两个缺陷:(a) 必须创建一个完整的 BindingContext (b) 必须知道数据元素的强类型。如果我必须这样做,我不介意做(a)[尽管它使该工具不像我想象的那样普遍适用]。但是 (b) 似乎是个炫技。
        • WCF 中的 MessageEncoder 类同时进行编码和解码。您也可以使用 ReadMessage() 方法()来阅读消息。
        • 玩了这个。它接近我正在寻找的东西。不幸的是,我发现的每一种使用 MessageEncoder 的方式在堆栈中都有一些点,如果不提前知道数据的强类型(catch-22),我就无法继续。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-02-18
        • 1970-01-01
        • 2012-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多