我正在尝试编写一个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服务的输出!
解码这种格式是否有秘密,或者我完全走错了路?
答案 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中显示。