2

(Similar to this question, however it ended up not being needed in that case)

I'm using an IErrorHandler implementation -- creating a FaultException so that clients to the service receive 'pretty' errors in the event of unhandled exceptions.

I'd now like to use an element in the request message and bounce it back in the FaultException and I'm not winning.

I can access the OperationContext.Current.RequestContext.RequestMessage, and can see the full envelope (including the 'message ID' property I want the FaultResponse to contain) using its ToString(), however I'd like to either 'manually' deserialize the request Message, or even just read the body as XML in order to extract the request property I'm looking for.

However I'm not able to read from the message, as (I assume) the Message has already been read somewhere previous in the WCF pipeline:

'The message cannot support the operation because it has been copied'

Here's my ProvideFault() implementation (I'm aware the XElement parsing may not even work in this form -- I'm not focusing on that now):

public void ProvideFault(Exception error, MessageVersion version, ref Message fault) {
    if (error is FaultException)
    {
        // Let WCF handle            
    }
    else
    {
        // Extract the 'messageID' element from the request message
        var req = OperationContext.Current.RequestContext.RequestMessage;

        // Throws the 'has been copied' exception
        XElement body = XElement.Parse(XElement.ReadFrom(req.GetReaderAtBodyContents()).ToString()); 

        var msgId = (string)body.Descendants("messageID").FirstOrDefault();

        var err = new FaultException<Foo>(new Foo
            {
                MessageID = msgId
            }, "Server error");

        var msgFault = err.CreateMessageFault();
        fault = Message.CreateMessage(
            version,
            msgFault,
            null);
    } }

What's the best way to do this?

EDIT: It doesn't look like it's possible to read the OperationContext's Request Message during the IErrorHandler events -- presumably because it's already been read earlier in the WCF pipeline (so creating a copy of the message using the MessageBuffer will not work). A 'workaround' to the problem is described in the accepted answer.

Community
  • 1
  • 1
Ive
  • 457
  • 5
  • 19
  • I think your problem is that you're reading the fault object prior to make a copy. It's a by ref stream, so if you read its content, it cause the error of 'The message cannot support the operation because it has been copied'. Try to copy it first, like this: MessageBuffer buffer = fault.CreateBufferedCopy(Int32.MaxValue); fault = buffer.CreateMessage(); Message messageFault = buffer.CreateMessage(); And use messageFault object instead – Ricardo Pontual Mar 17 '16 at 14:16
  • I'm not reading the Fault at all here (although in my actual implementation, I'm logging it) -- it's the request message that I'm trying to get a handle on. Calling CreateBufferedMessage() on the Request message also results in the 'already been copied' exception being raised. – Ive Mar 17 '16 at 14:26
  • I got. When exactly happens the error? – Ricardo Pontual Mar 17 '16 at 14:45
  • It's when I try to read (or copy) the Request Message. In my above code, it's on: req.GetReaderAtBodyContents() – Ive Mar 17 '16 at 15:00

3 Answers3

4

One possible approach is to use an IDispatchMessageInspector to read the values you require out of the request message and store them in OperationContext until you need to access them.

The MSDN documentation for IDispatchMessageInspector is straight forward. Just remember to first create a buffered copy of the message before reading it

public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)
{
    var bufferedCopy = request.CreateBufferedCopy(2000000); // choose a suitable buffer size
    request = bufferedCopy.CreateMessage();// Message for continued processing

    var messageToRead = bufferedCopy.CreateMessage(); // message to read properties from

    return null;
}

Refer to this question on how to attach your properties to OperationContext.

Community
  • 1
  • 1
Amith Sewnarain
  • 655
  • 6
  • 11
  • Thanks for the pointer to using the IExtension. I adapted my final solution to use this. As mentioned in the comment to @burning_LEGION, I did end up going this route of 'storing' the request properties in a DispatchMessageInspector, and then accessing them during the ProvideFault(). – Ive Mar 18 '16 at 13:21
  • The link for MSDN documentation is the same as the reference to the OperationContext. – Finickyflame Feb 28 '17 at 15:17
1

You should copy the buffer of original message:

// Read orignal messagem
var originalMessage = OperationContext.Current.RequestContext.RequestMessage;

// Copy message
MessageBuffer buffer = originalMessage.CreateBufferedCopy(Int32.MaxValue);
//OperationContext.Current.RequestContext.RequestMessage = buffer.CreateMessage();

var req = buffer.CreateMessage();
XElement body = XElement.Parse(XElement.ReadFrom(req.GetReaderAtBodyContents()).ToString());

Hope it helps.

Ricardo Pontual
  • 3,749
  • 3
  • 28
  • 43
0

You should customize your exception and use it properties to keep Id

public void ProvideFault(Exception error, MessageVersion version, ref Message fault) 
{
    var myException = error as MyException;
    if (myException == null)
    {
        return;           
    }

    var err = new FaultException<Foo>(new Foo
        {
            MessageID = myException.MsgId
        }, "Server error");

    var msgFault = err.CreateMessageFault();
    fault = Message.CreateMessage(
        version,
        msgFault,
        null);
} 
burning_LEGION
  • 13,246
  • 8
  • 40
  • 52
  • I'm trying to cater for unhanded exceptions; wrapping all exceptions in my own custom exception will involve 'manually' catching System.Exceptions and re-throwing my Custom Exception in EVERY service method...precisely what I'm trying to get away from. – Ive Mar 17 '16 at 14:05
  • Why am trying to get away from repeating the same custom-exception re-throwing in my N service methods? I'd rather handle this in a centralized location in a generic way and thought that the IErrorHandler was the place for it! – Ive Mar 17 '16 at 14:32
  • it's a bad idea imho, so i'm not sure that you can re-read message so try find required property, may be this one `OperationContext.Current.IncomingMessageHeaders.MessageId` – burning_LEGION Mar 17 '16 at 14:47
  • Thanks -- I'm not sure how good idea it is either; any client connecting should have the context of the request it sent, so I don't see bouncing back properties being much use. Unfortunately I'm re-implementing an existing webservice, and the requirement is for the API to remain absolutely unchanged. My example of 'messageID' is actually a simplification; there are about 5 properties that need to be set. However perhaps using Headers / Properties may be an idea: Intercept the request, add the neccessary properties that should then be readable during ProvideFault() via the OperationContext.. – Ive Mar 17 '16 at 14:56