none
Problem using XPath to investigate Message Headers.

    Question

  • I am having what appears to be a strange problem. My goal was to use XPath to investigate custom headers in a message. I would create an XPathDocument by passing it an XmlReader from the message.Headers.GetReaderAtHeader() method. Now, if I try to use an XPathNavigator and it's select methods on a Message object that was created by the WCF stack, it doesn't work. If I create a Message (the same EXACT message) via Message.CreateMessage(...) using a string representation of the SOAP message, the XPath queries work fine.

    I know why its not working, but not sure if its a bug or if there is a reason behind it. The Message instance that is built by the WCF stack is a BufferedMessage instance while that returned by Message.CreateMessage(...) is a StreamedMessage instance. When debugging, I noticed that the StreamedMessage version successfully parsed out my custom headers prefix and namespace (which is needed for XPath resolution) while the BufferedMessage version simply had tag names and no namespace information. For example, if I have a custom header that looks like this:

    <myPrefix:myHeader xmlns:myPrefix="uri:MyUri">
       <myPrefix:anotherTag />
    </myPrefix:myHeader>

    The StreamedMessage's HeaderInfo classes report the name as "myHeader" prefix "myPrefix" and namespaceURI "uri:MyUri". The BufferedMessage reports blank for everything but name, which is reported as "myPrefix:myHeader".

    Any comments/suggestions?

     

    Friday, July 14, 2006 7:28 PM

Answers

  • I think we're getting closer on this one.  One more request for a piece of info I should have asked for before.  What are the Name and Namespace properties on the custom header when it is created in the client?  If it matches the server that we know it's getting setup incorrectly.  If not, it's a transmission issue.

    In OnWriteStartHeader the writer is probably in the Content state from the previous node.  Try doing something like the following in OnWriteStartHeader:

    writer.WriteStartElement("app", "package", http://www.myaddress.com/app.xsd);

    This should give you the correct prefix binding.  Also, do you need the IsReferenceParameter?

    Check and make sure that the namespace/prefix data gets correctly received.

     

    Tuesday, July 25, 2006 1:02 AM
  • Glad I could help.

    Do you mean valid in that it did everything you wanted, or just that the streamed message could understand it?  I would expect msg.Find("package", http://www.myaddress.com/app.xsd) to fail because "package" is in the default namespace (which I don't think is set to what you're looking for a this point).

    Wednesday, July 26, 2006 11:40 PM

All replies

  • What does the buffered message look like?

    Thanks

    Sowmy

    Saturday, July 15, 2006 11:59 AM
    Moderator
  • This seems very odd.  Could you post the code you're using for the case that doesn't work, the message that is broken, and the version of the bits you are using?

    Thanks,

    Aaron

    Monday, July 17, 2006 4:07 PM
  • Hi, and sorry for the late reply. Before I post code, I decided to move to the June CTP to see if it fixes my problem. I had to port some changes, and have one issue that I can't seem to resolve. I was using the MatchAllEndpointBehavior. It was removed. Is there a replacement?
    Tuesday, July 18, 2006 11:20 AM
  • Yes.  It is still possible to change the dispatch filters using behaviors, but the common cases have been elevated to a property on ServiceBehavior.  Setting AddressFilterMode to Any is the equivalent of adding a MatchAllEndpointBehavior.

    -Aaron

    Tuesday, July 18, 2006 3:50 PM
  • Ok, I've made the change to AddressFilterMode and everything is back to how it was. Here is the text for the BufferedMessage with which I'm having my problem:

    <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/IEchoService/Echo</a:Action>
        <a:MessageID>urn:uuid:058b0039-1f25-437f-861a-8c66596300b3</a:MessageID>
        <a:ReplyTo>
          <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
        </a:ReplyTo>
        <app:package a:IsReferenceParameter="true" xmlns:app="http://www.myaddress.com/app.xsd">
          <app:subpackage1 id="57bab31f-2ca5-446d-8aef-29638073aa4d">
          </app:subpackage1>
          <app:subpackage2 id="f1ea25ba-413a-48b9-b5c0-f1bc5b716e5b">
          </app:subpackage2>
        </app:package>
        <a:To s:mustUnderstand="1">urn:EchoService</a:To>
      </s:Header>
      <s:Body>
        <Echo xmlns="http://tempuri.org/">
          <echoBack>Testing</echoBack>
        </Echo>
      </s:Body>
    </s:Envelope>

    I did narrow the problem down to another problem pre-XPath use. When I execute the following code:

    int packageIndex = message.Headers.FindHeader("package","http://www.myaddress.com/app.xsd");

    I get a result of -1. However, if I execute the following:

    message = Message.CreateMessage(XmlReader.Create(new StringReader(message.ToString())), 1000000, message.Headers.MessageVersion);
    int packageIndex = message.Headers.FindHeader("package","http://www.myaddress.com/app.xsd");

    Also, if I execute the following on the original message (without executing Message.CreateMessage) I also get 3:

    int packageIndex = message.Headers.FindHeader("app:package","");

    However, if I try to get a reader *suing GetReaderAtHeader(packageIndex) at header 3 via this method (not specifying the namespace), and use that to create an XPathDocument, none of my XPath queries resolve because the reader does not have namespace information. If I use the Message.CreateMessage workaround, and create a reader for header 3 from that message, all of my XPath queries resolve. Does that help?

    Wednesday, July 19, 2006 12:44 AM
  • OK.  I think we're homing in on the issue.  Off the top of my head, I'd say either the custom header's OnWriteXXX implementations have a bug, or the buffering process does.

    Do you have any custom channels on the stack that add this header?  What encoding/transport are you using?  Have you tried turning off logging/tracing?

    Somehow the localname of the header has become "app:package".  This could either be a bug in the product (calling Name instead of LocalName on the reader), or a bug in the header implementation.  You only get a BufferedMessage if you are using something that needs a copy of the message (like logging), so let's try turning that off and see if we can rule that out.

    Also check the implementation of your custom header.  The "Name" property is actually the local name, so if you're using a QName for this it could be the problem.  Also, if you override OnWriteStartHeader make sure you're calling the WriteStartElement overload with 3 arguments (assuming you want to use a specific prefix).

    If none of this issues turn out to be the problem please send me your full binding and any configuration and I'll try to replicate the issue locally.

    Wednesday, July 19, 2006 5:40 PM
  • No custom channels on the stack that add this header. The header is added by the client via a MessageInspector. I'm using Binary Encoding with a Named Pipe Transport. I do not have logging or tracing turned on.

    Now, since I don't have logging or tracing turned on, is there anything else that would give me a BufferedMessage instance? This could be a separate problem (or not) but if I resolve it this way at least its a temp fix. 

    What I'm writing is a SOAP intermediary, so I am receiving the message in a blind fashion; the intermediary doesn't care who the message is from or what its structured like. It only cares that certain header information exists. To accomplish this, the service is using a contract that has a catch all method whose Action is '*'. In addition, the ServiceBehavior has AddressFilterMode set to Any.

    Although the local name/QName comment could be something, it shouldn't be since it works in the StreamedMessage case. I do have something that may help, however. If I walkthrough the SOAP intermediaries execution, and I drill down through the BufferedMessage object through the Locals watch, I notice something strange. By going to message.Headers.Non-public Members.headers[3] (My header), I'm able to see that the debugger is catching InvalidCastExceptions at both the MessageHeader and ReadableHeader properties, with the detail of "Unable to cast object of type 'HeaderInfo' to type 'System.ServiceModel.Channels.MessageHeader'." on the MessageHeader property. If this doesn't help, I can send you whatever you need.

     

    Thanks

    Wednesday, July 19, 2006 8:02 PM
  • I did some poking around in the code and the BufferedMessage can also be created when using buffered mode in the transport, but I don't think that's the issue.  The reason the StreamedMessage case works is that the Xml isn't technically being created correctly, but winds up being valid.  The Xml resulting from msg.ToString() is what you wanted, so reparsing it in CreateMessage creates the message that was intended.

    Binary is much more permissive that Text.  It doesn't validate what isn't needed to parse the recieved bytes (for performance).  "a:b" is a valid localname since the prefix and localname are being sent as separate tokens in the protocol.  It doesn't need to be able to split them at a ":". 

    Given this and the behavior you describe I'm almost positive this is bug in how the custom header writes itself out.  What is your implementation like?  Do you use MessageHeader.CreateHeader, or do you have your own subclass?  If you're using CreateHeader, set a breakpoint on the call.  You should be passing the localname without a prefix.  If you have your own subclass, watch the Name property and OnWriteStartHeader method for similar things.

    As a side note, binary has optimizations in it for when it is allowed to choose its own prefixes.  If you don't actually need a particular prefix we recommend that you don't specify one.

    Wednesday, July 19, 2006 9:27 PM
  • I think you may have nailed it. I'm using "c:d" (I switched to "c:d" because I'm using the letter "a" later on in this post) on my call to CreateHeader specifically because I wanted the prefix on the "package" entity. The object I am passing to create header is an implementation of IXmlSerializable. On the WriteXml, I execute the following:

    writer.WriteAttributeString("xmlns","app",null,http://www.myaddress.com/app.xsd);

    I do this because I want to define the "app" prefix since I use it in package's sub-elements. I know it's a work around (look at the WriteAttributeString parameter list).

    If I use just "d" rather than "c:d" I do not get the prefix (obviously) but more importantly, during serialization, I end up with two prefix definitions in the package element: one for my "app" prefix because of my WriteAttributeString and one for the prefix "a" (WCF default). If I leave CreateHeader with "c:d" the definition for prefix "a" is left off.

    Is there anyway then to define *just* my prefix and not use "c:d" on CreateHeader? In my case I do need a prefix because of contextual guarantees I must make in the SOAP message.

    Wednesday, July 19, 2006 9:50 PM
  • If you need to use a specific prefix you won't be able to do it with CreateHeader.  I'd recommend writing a subclass of MessageHeader (should be simple).  In OnWriteStartHeader you'll need to call WriteStartElement as appropriate (and define whatever else you need).  In OnWriteHeaderContents just create a serializer and call WriteObjectContent.

    The CreateHeader methods were intended to be simple helpers for commonly created headers.  We considered scenarios where you needed control over prefix selection more advanced.

    Wednesday, July 19, 2006 10:17 PM
  • Aaron, I switched to extending MessageHeader, and still no luck. I experience the same behavior when trying to get a reader at the header. It says it can't find it. I'm not sure that it matters how the header is being built on the client (for example, it could be a Java client which obviously doesn't have MessageHeader). The only difference I see is that now "package" does not have the prefix, which is fine, and it is defined only in the XMLNS string. Any other suggestions?
    Friday, July 21, 2006 4:12 PM
  • This feels like it's related to binary.  Not that there's a bug in binary, but that the reduced validation that goes along with binary is causing some other bug to slip through and pop up later.  You can try switching to the text encoder and seeing if anything changes.  Also, are you ever calling WriteStream?

    Let's try figuring out what everything looks like.  Using your custom header, set a breakpoint where you do the Find and locate the header in the msg.Headers collection.  Post the values of the Name and Namespace properties along with the new msg.ToString().  If you would also paste the implementation of you OnWriteStartHeader method it may give me some insight.

    Friday, July 21, 2006 7:54 PM
  • I tried switching to the TextEncoder, and nothing changed. I have the following information for Name & Namespace with a breakpoint at the find:

    Name = "package"
    Namespace = ""

    Please note that Namespace is a blank string, not null. Here is the new msg.ToString()

    <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/IEchoService/Echo</a:Action>
        <a:MessageID>urn:uuid:058b0039-1f25-437f-861a-8c66596300b3</a:MessageID>
        <a:ReplyTo>
          <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
        </a:ReplyTo>
        <package a:IsReferenceParameter="true" xmlns:app="http://www.myaddress.com/app.xsd">
          <app:subpackage1 id="57bab31f-2ca5-446d-8aef-29638073aa4d">
          </app:subpackage1>
          <app:subpackage2 id="f1ea25ba-413a-48b9-b5c0-f1bc5b716e5b">
          </app:subpackage2>
        </package>
        <a:To s:mustUnderstand="1">urn:EchoService</a:To>
      </s:Header>
      <s:Body>
        <Echo xmlns="http://tempuri.org/">
          <echoBack>Testing</echoBack>
        </Echo>
      </s:Body>
    </s:Envelope>

    I did not overload OnWriteStartHeader because the writer is in 'Content' state at that poinjt, which does not allow me to modify the xlmns attribute.l Instead, I used the following MessageHeader override:

    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        writer.WriteXmlnsAttribute(
    "app", http://www.myaddress.com/app.xsd);
        writer.WriteAttributeString("a", "IsReferenceParameter", null, "true");

        if(subpackage != null)
        {
           
    subpackage.WriteXml(writer);
        }
    }

    It's a quite simple implementation. Should I not be using 'WriteXmlnsAttribute'? Let me know if this helps.

    Monday, July 24, 2006 1:33 PM
  • I think we're getting closer on this one.  One more request for a piece of info I should have asked for before.  What are the Name and Namespace properties on the custom header when it is created in the client?  If it matches the server that we know it's getting setup incorrectly.  If not, it's a transmission issue.

    In OnWriteStartHeader the writer is probably in the Content state from the previous node.  Try doing something like the following in OnWriteStartHeader:

    writer.WriteStartElement("app", "package", http://www.myaddress.com/app.xsd);

    This should give you the correct prefix binding.  Also, do you need the IsReferenceParameter?

    Check and make sure that the namespace/prefix data gets correctly received.

     

    Tuesday, July 25, 2006 1:02 AM
  • Thanks Aaron, it finally works! Your OnWriteStartHeader mod fixed it. I walked through to notice the differences. I had a mistake in my post, but in my first sample message, I said the SOAP message had:

    <app:package a:IsReferenceParameter="true" xmlns:app="http://www.myaddress.com/app.xsd">
     but in fact it was:

    <package a:IsReferenceParameter="true" xmlns:app="http://www.myaddress.com/app.xsd">

    Now, using your OnWriteStartHeader approach, it adds the prefix and resolves fine. Either way, shouldn't it have worked though? It still seems to only happen with the Buffered Message since

    <package a:IsReferenceParameter="true" xmlns:app="http://www.myaddress.com/app.xsd">

    is considered valid with the StreamedMessage.

    Wednesday, July 26, 2006 1:19 AM
  • Glad I could help.

    Do you mean valid in that it did everything you wanted, or just that the streamed message could understand it?  I would expect msg.Find("package", http://www.myaddress.com/app.xsd) to fail because "package" is in the default namespace (which I don't think is set to what you're looking for a this point).

    Wednesday, July 26, 2006 11:40 PM
  • I am tying to add IsReferenceParameter attribute to Meassge header.

    Can you please give some code sample to add IsReferenceParameter with namespace like this.

    xmlns:wsa="http://www.w3.org/2005/08/addressing" 
       wsa
    :IsReferenceParameter="1" 

     

     

    Tuesday, March 08, 2011 4:58 AM