2

When creating a generic fault exception FaultException<MyWebServiceFaultDetail> and passing it back to the message inspector pipeline as a MessageFault, the client will not receive the generic fault in its catch (FaultException<MyWebServiceFaultDetail> ex) block, it is only caught within the catch (FaultException ex) block.

The MyWebServiceFaultDetail and IClientMessageInspector implication both live within the same project as the client WCF web reference in a single project MyProjects.MyWebService.

The WebService is called by another project which has a reference to the MyProjects.MyWebService project.

*Comments have been removed for brevity

The data contract:

[DataContract]
public class MyWebServiceFaultDetail
{
    [DataMember]
    public string MessageDetail { get; set; }

    [DataMember]
    public string MessageType { get; set; }

    [DataMember]
    public string TransactionComplete { get; set; }

    [DataMember]
    public string TransactionSuccess { get; set; }

    public override string ToString()
    {
        return string.Format("Detail[MessageDetail={0}] [MessageType={1}] [TransactionComplete={2}] [TransactionSuccess={3}]", MessageDetail,MessageType ,TransactionComplete ,TransactionSuccess );
    }
}

The message inspector. I would just add that reply.Headers.Action is null when this method runs. Setting the action value on the call to CreateMessage() has had no affect. Values I have tried.

.CreateMessage(reply.Version, ex.CreateMessageFault(), "*");

.CreateMessage(reply.Version, ex.CreateMessageFault(), reply.Headers.Action);

.CreateMessage(reply.Version, ex.CreateMessageFault(), ex.Action);

internal class ResponseMessageInspector : System.ServiceModel.Dispatcher.IClientMessageInspector
{
    private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        MessageBuffer bufferedMessage = null;

        try
        {
            bufferedMessage = reply.CreateBufferedCopy(Int32.MaxValue);

            Message replacedMessage = bufferedMessage.CreateMessage();

            if (bufferedMessage.MessageContentType != "application/soap+msbin1" || reply.IsEmpty || reply.IsFault)
            {
                reply = replacedMessage;
                return;
            }

            bool isErrorMessage;

            var messageReader = replacedMessage.GetReaderAtBodyContents();

            isErrorMessage = (messageReader.Name == "TransactionReport");

            if (isErrorMessage)
            {
                string transactionComplete = "",
                    transactionSuccess = "",
                    messageType = "",
                    messageDetail = "",
                    messageBrief = "";

                while (messageReader.Read())
                {
                    if (messageReader.NodeType == XmlNodeType.Element && !messageReader.IsEmptyElement)
                    {
                        switch (messageReader.Name)
                        {
                            case "TransactionComplete":
                                transactionComplete = messageReader.ReadString();
                                break;

                            case "TransactionSuccess":
                                transactionSuccess = messageReader.ReadString();
                                break;

                            case "MessageType":
                                messageType = messageReader.ReadString();
                                break;

                            case "MessageDetail":
                                messageDetail = messageReader.ReadString();
                                break;

                            case "MessageBrief":
                                messageBrief = messageReader.ReadString();
                                break;

                            default:
                                break;
                        }
                    }
                }

                if (string.IsNullOrEmpty(messageBrief))
                {
                    messageBrief = "My response processing fault: {Unable to obtain error message from My response, enable WCF message tracing for more detailed information}";

                    _logger.Warn(messageBrief);
                }

                FaultException ex = new FaultException<MyWebServiceFaultDetail>(
                    new MyWebServiceFaultDetail
                    {
                        TransactionComplete = transactionComplete,
                        TransactionSuccess = transactionSuccess,
                        MessageDetail = messageDetail,
                        MessageType = messageType
                    },
                    new FaultReason(messageBrief));

                Message faultMessage = Message.CreateMessage(reply.Version, ex.CreateMessageFault(), null);

                faultMessage.Headers.CopyHeadersFrom(reply.Headers);
                faultMessage.Properties.CopyProperties(reply.Properties);

                reply = faultMessage;
            }
            else
                reply = bufferedMessage.CreateMessage();
        }
        finally
        {
            if (bufferedMessage != null)
                bufferedMessage.Close();
        }
    }

    public object BeforeSendRequest(ref Message request, System.ServiceModel.IClientChannel channel)
    {
        return null;
    }
}

The client code that is reciving the FaultException but not a FaultException<MyWebServiceFaultDetail>

 internal static T TrySendToMyWebService<T>(
        CallWebServiceDelegate<T> callWebService,
        bool expectResponce,
        out MessageProcessorResult result) where T : class
    {
        T MyWebServiceResponce = null;

        result = new MessageProcessorResult();

        using (ServiceRequestConnectorServiceSoapClient ws =
          new ServiceRequestConnectorServiceSoapClient())
        {
            try
            {
                MyWebServiceWebServiceHelper.LogOn(ws);
                MyWebServiceResponce = callWebService(ws);
                if (expectResponce && MyWebServiceResponce == null)
                {
                    result.ShouldRetry = true;
                    result.RetryReason = "Unexpected MyWebService web service response. The response was null";
                }
            }
            catch (FaultException<MyWebServiceFaultDetail> ex)
            {
                // I never get called :(
                result.Exception = ex;
                result.ShouldRetry = true;
                result.RetryReason = string.Format("An Exception was raised calling the MyWebService web service: Reason:{0}  /r/nDetails: {1}", ex.Reason, ex.Detail.ToString());
                _logger.ErrorException(result.RetryReason, ex);
            }
            catch (FaultException ex)
            {
                result.Exception = ex;
                result.ShouldRetry = true;
                result.RetryReason = string.Format("An Exception was raised calling the MyWebService web service: {0}", ex.Message);
                _logger.ErrorException(ex.Message, ex);
            }
            finally
            {
                MyWebServiceWebServiceHelper.LogOff(ws);
            }
        }

        return MyWebServiceResponce;
    }
Microsoft Developer
  • 1,919
  • 1
  • 20
  • 27

2 Answers2

0

The problem could be here as you are specifying 2 format arguments and only supplying 1:

public override string ToString()
{
    return string.Format("Detail[MessageDetail={0}] [MessageType={1}] [TransactionComplete={1}] [TransactionSuccess={1}]", MessageDetail);
}

So you actually get a FormatException thrown by string.Format rather than your FaultException<JoBlogsWebServiceFaultDetail>.

Trevor Pilley
  • 16,156
  • 5
  • 44
  • 60
  • Yes, you can refer to the same format argument multiple times `[TransactionComplete={1}] [TransactionSuccess={1}]` but it's not a valid format call if you are referencing `{0}` and `{1}` but only supplying one format argument `, MessageDetail`. `MessageDetail` would be used as `{0}`, but there is no parameter for `{1}`. – Trevor Pilley Nov 06 '12 at 15:51
  • Agreed that would raise a zero based index error, however It has somehow not been raised back to the channel. Hummm. I'll correct the code and get back to you. – Microsoft Developer Nov 06 '12 at 15:58
  • Sorry running between projects. The call to ToString is happening in the client so any error would be raised within the catch block. The catch block which is incorrectly being invoked is the FaultException catch block not the FaultException. After fixing the ToString sadly it does not make WCF raise the fault to the correct FaultException catch block in the client. It still wants to raise a standard FaultException with no JoBlogsWebServiceFaultDetail . – Microsoft Developer Nov 06 '12 at 17:27
  • Just a note that I have corrected the code example above in the ToString method, thanks for pointing that out. – Microsoft Developer Nov 09 '12 at 09:20
0

Just throw the new typed FaultException<MyWebServiceFaultDetail> from the AfterReceiveReply of your message inspector.

FaultException ex = new FaultException<MyWebServiceFaultDetail>(
                new MyWebServiceFaultDetail
                {
                    TransactionComplete = transactionComplete,
                    TransactionSuccess = transactionSuccess,
                    MessageDetail = messageDetail,
                    MessageType = messageType
                },
                new FaultReason(messageBrief));
throw ex;

If your detail class is declared on the client side, you don't even need to decorate it with DataContract attribute.

Paciv
  • 1,487
  • 10
  • 16