使用MessageInspector验证WCF Web服务XML正文

时间:2010-02-01 17:23:02

标签: .net wcf web-services validation xsd

我针对使用XmlSerializer序列化程序的预先存在的XSD架构构建了一个WCF Web服务。

我想针对此预先存在的架构验证传入请求和传出请求。 MSDN包含一个如何使用WCF MessageInspectors完成此操作的实例。所描述的技术涉及在主体内容上创建XmlReader

XmlReader bodyReader = message.GetReaderAtBodyContents().ReadSubtree();

然后使用此读者使用SchemaSet创建对XMLDictionaryReader进行验证。

我遇到了一个问题,即我的xml正文内容包含多个xsi:type="xsd:string"针对元素的实例。 xsixsd的名称空间前缀由WCF针对body元素生成,因此我的验证因xsd未被声明而失败。

示例XML消息:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
        <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://www.abc.com/Service/Response</Action>
    </s:Header>
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <foo xmlns="http://www.abc.com">
            <aaa xsi:type="xsd:string">true</aaa>
        </foo>
    </s:Body>
</s:Envelope>

验证错误:

"The value 'xsd:string' is invalid according to its schema type 'QName' - 'xsd' is an undeclared namespace."

是否有任何WCF配置选项允许我将这些xmlns声明下推到正文中?

1 个答案:

答案 0 :(得分:1)

好的,我知道很久以前就问过这个问题,但我刚遇到同样的问题,所以我想我会在这里发布我的发现。

由于xsi和xsd命名空间位于Body元素上,因此message.GetReaderAtBodyContents()方法不会返回有效的xml。我找到了两种方法来解决这个问题。

首先,您可以将调用包装在包含xsi和xsd命名空间的自己的元素中,然后可以从中提取内部xml。这会导致命名空间在使用时被限定。

XmlReader bodyReader = message.GetReaderAtBodyContents();
// Next we wrap the possibly invalid body contents (because of missing namespaces) into our own wrapper with the namespaces specified
XmlDocument bodyDoc = new XmlDocument();
MemoryStream bodyMS = new MemoryStream();
XmlWriter w = XmlWriter.Create(bodyMS, new XmlWriterSettings {Indent = true, IndentChars = "  ", OmitXmlDeclaration = true});
w.WriteStartElement("body");
w.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
w.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");
while (xdr.NodeType != XmlNodeType.EndElement && xdr.LocalName != "Body" && xdr.NamespaceURI != "http://schemas.xmlsoap.org/soap/envelope/")
{
    if (xdr.NodeType != XmlNodeType.Whitespace)
    {
        w.WriteNode(xdr, true);
    }
    else
    {
        xdr.Read();
    }
}
w.WriteEndElement();
w.Flush();
bodyMS.Position = 0;
bodyDoc.Load(bodyMS);
XmlNode bodyNode = bodyDoc.SelectSingleNode("body");
string innerBody = bodyNode.InnerXml;

如果检查innerBody,您将看到xsi和xsd命名空间已经在使用它们的每个节点上进行了限定,因此您可以将innerBody加载到读取器中进行验证。

其次,您可以将整个消息读入xml并提取正文内容,如上所述。这将具有与上面相同的效果,但将处理Body元素上的任何名称空间。

StringBuilder sb = new StringBuilder();
using (System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(sb))
{
    message.WriteMessage(xw);
}
string theWholeMessage = sb.ToString();
XmlDocument wholeBodyDoc = new XmlDocument();
wholeBodyDoc.LoadXml(theWholeMessage);
XmlNamespaceManager wholeNS = new XmlNamespaceManager(new NameTable());
wholeNS.AddNamespace("s", "http://www.w3.org/2003/05/soap-envelope");
XmlNode wholeBodyNode = wholeBodyDoc.SelectSingleNode("//s:Body", wholeNS);
string innerBody = wholeBodyNode.InnerXml;

这里我只是将整个消息加载到字符串构建器中,然后将其加载到XmlDocument中,这样我就可以提取Body元素的内部xml。生成的xml将与第一种方法相同。