Extracting detail from a WCF FaultException response - c#

I am successfully working with a third party soap service. I have added a service reference to a soap web service which has auto generated the classes.
When an error occurs it returns a soap response like this:
<SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Client</faultcode>
<faultstring xsi:type="xsd:string">Error while reading parameters of method 'Demo'</faultstring>
<detail xsi:type="xsd:string">Invalid login or password. Connection denied.</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I can catch the error but not extract the detail. I have tried the following code:
catch (FaultException ex)
{
MessageFault msgFault = ex.CreateMessageFault();
var elm = msgFault.GetDetail<string>();
//throw Detail
}
However it Errors with the following as detail node is not an object:
Expecting element 'string' from namespace 'http://schemas.datacontract.org/2004/07/MyDemoNamespace'.. Encountered 'Text' with name '', namespace ''.
This is third party API so I cannot change the response.

The detail node of the message fault is expected to contain XML. The GetDetail will deserialize this XML into the given object.
As the contents is not XML it was possible to use this method.
You can however get access to the XML and read the innerXml value:
MessageFault msgFault = ex.CreateMessageFault();
var msg = msgFault.GetReaderAtDetailContents().Value;
This approached worked.

Here's a few methods I've found of extracting that detailed exception information from FaultExceptions
Get the String Contents of a Single Element
catch (FaultException e)
{
var errorElement = XElement.Parse(e.CreateMessageFault().GetReaderAtDetailContents().ReadOuterXml());
var errorDictionary = errorElement.Elements().ToDictionary(key => key.Name.LocalName, val => val.Value);
var errorMessage = errorDictionary?["ErrorMessage"];
}
Example Output:
Organization does not exist.
Get the String Contents of a All Details as a Single String
catch (FaultException e)
{
var errorElement = XElement.Parse(e.CreateMessageFault().GetReaderAtDetailContents().ReadOuterXml());
var errorDictionary = errorElement.Elements().ToDictionary(key => key.Name.LocalName, val => val.Value);
var errorDetails = string.Join(";", errorDictionary);
}
Example Output:
[ErrorMessage, Organization does not exist.];[EventCode, 3459046134826139648];[Parameters, ]
Get the String Contents of a Everything as an XML string
var errorElement = XElement.Parse(e.CreateMessageFault().GetReaderAtDetailContents().ReadOuterXml());
var xmlDetail = (string)errorElement;
Example Output:
<FaultData xmlns="http://schemas.datacontract.org/2004/07/Xata.Ignition.Common.Contract" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ErrorMessage>Organization does not exist.</ErrorMessage>
<EventCode>3459046134826139648</EventCode>
<Parameters i:nil="true" xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"></Parameters>
</FaultData>

The following should give you the value of the detail element of the FaultException.
var faultMessage = faultException.CreateMessageFault();
if(faultMessage.HasDetail){
Console.Write(faultMessage.GetDetail<XElement>().Value);
}

public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
if (reply.IsFault)
{
// Create a copy of the original reply to allow default WCF processing
MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
Message copy = buffer.CreateMessage(); // Create a copy to work with
reply = buffer.CreateMessage(); // Restore the original message
MessageFault faultex = MessageFault.CreateFault(copy, Int32.MaxValue); //Get Fault from Message
FaultCode codigo = faultex.Code;
//if (faultex.HasDetail)... //More details
buffer.Close();

You can catch FaultException<TDetail>, which gives you detail for free.
catch (FaultException<string> ex)
{
string yourDetail = ex.Detail;
}

Related

Gmail API Message Payload.Data Comes Empty

I'm trying to read the messages from the Google API but when I check the Payload.Body.Data to see the content of the email always comes null.
But when I use Raw format I see the body but it's a little complicated to handle the information because it's in a huge and dirty string.
public static Message GetMessage(GmailService service, String userId, String messageId)
{
try
{
var request = service.Users.Messages.Get(userId, messageId);
request.Format = Google.Apis.Gmail.v1.UsersResource.MessagesResource.GetRequest.FormatEnum.Full;
var message = request.Execute();
return message;
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
return null;
}

Send message with actionresult

I need to return the server error from azure functions.
Now I implement the same using InternalServerErrorResult(). It only sends the error code and no response/message can be sent with this function.
How to implement an exception handler where the error code and message can be sent together using actionresult in azure functions
current implementation
catch (Exception ex)
{
log.LogInformation("An error occured {0}" + ex);
//json = new Response(ex.StackTrace, AppConstants.ErrorCodes.SystemException).SerializeToString();
return (ActionResult)new InternalServerErrorResult();
}
this returns with an empty response in postman with error 500
Note that this is from Microsoft.AspNetCore.Mvc namespace:
var result = new ObjectResult(new { error = "your error message here" })
{
StatusCode = 500
};
Based on configured formatters it will return serialized object to client.
For JSON (it's default) it will return following:
{ "error" : "your error message here" }
To send a message with the status code you can use return StatusCode(httpCode, message), which is an ObjectResult.
For example:
return StatusCode(500, "An error occurred");
You can also pass an object (example using HttpStatusCode enum):
return StatusCode((int)HttpStatusCode.InternalServerError, json);

Twilio TwiML app XML error

I have a TwiML app with this code in the Connect action of the CallController. This code is taken straight from the Twilio demos.
[HttpPost]
public virtual ActionResult Connect(string phoneNumber, string called)
{
var response = new VoiceResponse();
var dial = new Dial(callerId: "+6138595????");
if (phoneNumber != null)
{
dial.Number(phoneNumber);
}
else
{
dial.Client("support_agent");
}
response.Dial(dial);
return TwiML(response);
}
When this is called it raises the error "Data at the root level is invalid. Line 1, position 1."
The XML this generates is
<?xml version="1.0" encoding="utf-8"?>
<Response>
<Dial callerId="+6138595????">
<Client>support_agent</Client>
</Dial>
</Response>
Twilio Evangelist here.
A quick question - is this happening every time the method is invoked, or only when specific inputs are provided? Needing to build the string manually is of course not desired. So I would like to get to the bottom of what triggered this result.
I have found I can fix it by replacing
return TwiML(response);
with
return new TwiMLResult(response.ToString(), new UTF8Encoding());
Appears to be some kind of encoding issue using the first method.
I had the same problem we solved it in our WebApi by skipping the Twilio sdk and generating the xml by ourselves.
I hope this will work for you too:
[HttpPost]
public virtual HttpResponseMessage Connect(string phoneNumber, string called)
{
string twiml = $"<?xml version=\"1.0\" encoding=\"utf-8\"?><Response><Dial callerId=\"{phoneNumber}\"><Client>support_agent</Client></Dial></Response>";
var xmlResponse = new HttpResponseMessage();
xmlResponse.Content = new StringContent(twiml, Encoding.UTF8, "text/xml");
return xmlResponse;
}
Please notice that there are no end of lines - "\n", \r", etc.

How to return error message from Catch block. Right now empty is returned

My sample code of ApiKey validation is given below (I am using MVC4 web api RC):
public class ApiKeyFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext context)
{
//read api key from query string
string querystring = context.Request.RequestUri.Query;
string apikey = HttpUtility.ParseQueryString(querystring).Get("apikey");
//if no api key supplied, send out validation message
if (string.IsNullOrWhiteSpace(apikey))
{
var response = context.Request.CreateResponse(HttpStatusCode.Unauthorized, new Error { Message = "You can't use the API without the key." });
throw new HttpResponseException(response);
}
else
{
try
{
GetUser(decodedString); //error occurred here
}
catch (Exception)
{
var response = context.Request.CreateResponse(HttpStatusCode.Unauthorized, new Error { Message = "User with api key is not valid" });
throw new HttpResponseException(response);
}
}
}
}
Here problem is with Catch block statement. I Just wanted to send custom error message to user. But nothing is sent. It displays a blank screen
However, the statement below is working well and sends out the validation error message correctly:
if (string.IsNullOrWhiteSpace(apikey))
{
var response = context.Request.CreateResponse(HttpStatusCode.Unauthorized, new Error { Message = "You can't use the API without the key." });
throw new HttpResponseException(response);
}
Is there anything that i am doing wrong.
I was having the same issue in the exact same scenario. However, in this scenario, you need to return some content in your response to be shown and not really throw the exception. So based on this, I would change your code to the following:
catch (Exception)
{
var response = context.Request.CreateResponse(httpStatusCode.Unauthorized);
response.Content = new StringContent("User with api key is not valid");
context.Response = response;
}
So with this change you are now returning your response, with content that will displayed in place of the blank screen.

WCF set custom header - reading not working

I need to put custom headers into WCF. My Code is as follows:
ServiceReference1.Service2Client ws = new Service2Client();
using (OperationContextScope scope = new OperationContextScope((IContextChannel)ws.InnerChannel))
{
MessageHeaders messageHeadersElement = OperationContext.Current.OutgoingMessageHeaders;
messageHeadersElement.Add(MessageHeader.CreateHeader("Authorization", String.Empty, "string"));
messageHeadersElement.Add(MessageHeader.CreateHeader("username", String.Empty, "user"));
var res = ws.GetUser("123");
}
But when I try to read it in the service, nothing is availabe in the following
public class OAuthAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
int index = OperationContext.Current.IncomingMessageHeaders.FindHeader("username", String.Empty);
string auth = operationContext.IncomingMessageHeaders.GetHeader<string>("username", String.Empty);
var hereIseeIt = operationContext.RequestContext.RequestMessage;
index is -1: not found
auth: is also displaying an exception that the header is not available
hereIseeIt: .ToString() shows a xml where I can see that user is existent, but I see no way to access that information in any of the objects
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<username xmlns="http://Microsoft.WCF.Documentation">user</username>
</s:Header>
<s:Body>
<GetUser xmlns="http://tempuri.org/">
<UserId>123</UserId>
</GetUser>
</s:Body>
</s:Envelope>
But I cannot access them since I find no way to access the s:Header ...
try using:
XPathNavigator XPN = operationContext.RequestContext.RequestMessage.CreateBufferedCopy ().CreateNavigator ();
NOT elegant but it gives you the whole Message accessible through a XPathNavigator which should make it easy to get to any value inside the Message you want..
some links:
http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.requestcontext.aspx
http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.message.aspx
http://msdn.microsoft.com/en-us/library/system.xml.xpath.xpathnavigator.aspx
Here's an easy way to get the inner XML of the username header for your scenario. Even if you already solved your issue a long time ago, I thought it might help somebody else who faces the same issue.
var username = String.Empty;
// using the namespace from you XML sample
var usernameHeaderPosition = OperationContext.Current
.IncomingMessageHeaders
.FindHeader("username", "http://Microsoft.WCF.Documentation");
if (usernameHeaderPosition > -1)
{
username = OperationContext.Current
.IncomingMessageHeaders
.GetReaderAtHeader(usernameHeaderPosition).ReadInnerXml();
}

Categories