11

I am running a ServiceHost to test one of my services and all works fine until I throw a FaultException - bang I get XML not JSON

my service contract - lovely

   /// <summary>
    ///   <para>Get category by id</para>
    /// </summary>
    [OperationContract(AsyncPattern = true)]
    [FaultContract(typeof(CategoryNotFound))]
    [FaultContract(typeof(UnexpectedExceptionDetail))]
    IAsyncResult BeginCategoryById(
        CategoryByIdRequest request,
        AsyncCallback callback, object state);

    CategoryByIdResponse EndCategoryById(IAsyncResult result);

Host Set-up - scrummy yum

var host = new ServiceHost(serviceType, new Uri(serviceUrl));
host.AddServiceEndpoint(
    serviceContract,
    new WebHttpBinding(), "")
        .Behaviors.Add(
             new WebHttpBehavior
                            {
                                DefaultBodyStyle = WebMessageBodyStyle.Bare,
                                DefaultOutgoingResponseFormat = WebMessageFormat.Json,
                                FaultExceptionEnabled = true
                            });

host.Open();

Here's the call - oo belly ache

var request = WebRequest.Create(serviceUrl + "/" + serviceName);
request.Method = "POST";
request.ContentType = "application/json; charset=utf-8";
request.ContentLength = 0;

try
{
    // receive response
    using (var response = request.GetResponse())
    {
        var responseStream = response.GetResponseStream();

        // convert back into referenced object for verification
        var deserialiser = new DataContractJsonSerializer(typeof (TResponseData));
        return (TResponseData) deserialiser.ReadObject(responseStream);
    }
}
catch (WebException wex)
{
    var response = wex.Response;

    using (var responseStream = response.GetResponseStream())
    {
        // convert back into fault
        //var deserialiser = new DataContractJsonSerializer(typeof(FaultException<CategoryNotFound>));
        //var fex = (FaultException<CategoryNotFound>)deserialiser.ReadObject(responseStream);

        var text = new StreamReader(responseStream).ReadToEnd();
        var fex = new Exception(text, wex);    

        Logger.Error(fex);
        throw fex;
    }
}

the text var contains the correct fault, but serialized as Xml What have I done wrong here?

Anthony Johnston
  • 9,405
  • 4
  • 46
  • 57

6 Answers6

3

The answer is to implement an IErrorHandler and supporting behavior

I found this excellent post by iainjmitchell

http://iainjmitchell.com/blog/?p=142

Anthony Johnston
  • 9,405
  • 4
  • 46
  • 57
  • 2
    The hard bit is how to throw a FaultException for SOAP clients and WebHttpException for REST clients... I haven't figured that one out. – Rebecca Aug 25 '11 at 11:18
  • @Junto, did you ever figure this out? Is it simply providing a separate endpoint for each client with the necessary behaviour config for each? – Mr Moose Mar 28 '12 at 08:30
  • @MrMoose I have an open question here: http://stackoverflow.com/questions/7188519/wcf-ierrorhandler-to-return-faultexception-to-soap-and-webhttpexception-to-pox-a . Have you tried throwing a new WebHttpException() { ... }; and see what happens in the SOAP client? – Rebecca Jun 22 '12 at 08:50
2

I can happily present the solution. I had exactly the same problem and after i messed a little with my endpoint behavior configuration i discovered the needed config element. The solution is to force wcf to use the selected format (json):

 <behavior name="ExtendedJSONBehavior">
     <webHttp defaultOutgoingResponseFormat="Json" defaultBodyStyle="Wrapped" automaticFormatSelectionEnabled="false"/>
 </behavior>

As you can see, the key was the "automaticFormatSelectionEnabled" attribute.

Have fun with wcf again

Firzen
  • 21
  • 2
  • i checked the attribute again. This attribute is supported in .Net 4.0. Do you use 4.0? And try to throw "WebFaultException" instead of FaultException. I'm using FaulException from .Net to .Net and WebFaultException from .Net to Browser. – Firzen Jul 27 '11 at 17:53
  • I have the same problem but I use .Net 3.5 and I know that these attributes only exists in .Net 4.0. What can I do to acheive this in .Net 3.5 (see my question about this at http://stackoverflow.com/questions/12095621/consume-json-wcf-service-with-net-3-5-client) – mberube.Net Aug 23 '12 at 16:23
0
//由于调用 ProvideFault 时,客户端处于阻塞状态,不要在这里进行长时间的操作
public void ProvideFault(Exception error, MessageVersion version, ref Message msg)
{
    //避免敏感信息泄漏,例如:数据库配置, error包含的错误信息应该记录到服务器的日志中,不能显示给客户端
    // FaultException<int> e = new FaultException<int>(123, error.Message);
    DateTime now = DateTime.Now;
    time = now.ToString("yyyyMMddHHmmssfff", DateTimeFormatInfo.InvariantInfo);// "" + now.Year.ToString() + now.Month.ToString() + now.Day.ToString() + now.Hour.ToString() + now.Minute.ToString() + now.Second.ToString() + now.Millisecond.ToString();
    string errorMsg = "服务内部错误_" + time;
    // FaultException fe = new FaultException(errorMsg);
    // MessageFault mf = fe.CreateMessageFault();
    // msg = Message.CreateMessage(version, mf, fe.Action);

    //The fault to be returned
    msg = Message.CreateMessage(version, "", errorMsg, new DataContractJsonSerializer(typeof(string)));

    // tell WCF to use JSON encoding rather than default XML
    WebBodyFormatMessageProperty wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);

    // Add the formatter to the fault
    msg.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);

    //Modify response
    HttpResponseMessageProperty rmp = new HttpResponseMessageProperty();

    // return custom error code, 400.
    rmp.StatusCode = System.Net.HttpStatusCode.InternalServerError;
    rmp.StatusDescription = "Bad request";

    //Mark the jsonerror and json content
    rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
    rmp.Headers["jsonerror"] = "true";

    //Add to fault
    msg.Properties.Add(HttpResponseMessageProperty.Name, rmp);
}
0

This probably won't give you the "why" part. Take a look a this question. It lets you grab and reformat the faults before they go out as a response. This will at least Json-ize your responses enough to get you going. this also has a similar idea behind it.

Community
  • 1
  • 1
dawebber
  • 3,503
  • 1
  • 16
  • 16
  • Thanks for that, may have to do something similar - but it looks like they are catching any old .net exception and making it JSON - my exceptions are proper FaultExceptions and I want the Config to define how they are returned, in this case JSON, but it may be deployed to somewhere which wants xml - I suppose I can code that too, but it seems that I am missing something simple which will make WCF behave as I need - again thanks for the links – Anthony Johnston Jun 22 '11 at 13:53
0

According to MSDN documentation for DataContractJsonSerializer:

"If an error occurs during the serialization of an outgoing reply on the server or the reply operation throws an exception for some other reason, it may not get returned to the client as a fault."

Also, and this is mere speculation, but it almost looks like this serializer serializes to XML and then converts it to JSON. So when your Fault happens, it gets interrupted mid-process? Then again I could be totally wrong.

Good luck.

Alec
  • 1,646
  • 3
  • 19
  • 35
  • It is being returned to the client as a Fault - just serialized as Xml. Also, I'm not sure if an error occurred in the speculative bit you mention, but, if it did, then would it return at all? Ta anyway – Anthony Johnston Jun 22 '11 at 14:48
0

I don't understand why you are using WebRequest to make calls to a WCF service. Is there a specific reason for this? How do you know when you handle that WebException that it will be a FaultException<CategoryNotFound>?
If you use a service proxy, and your service throws FaultException<T>, it's probably better to write your try-catch like this:

try
{
    //Do service call
} 
catch (FaultException<CategoryNotFound> fe)
{
    //handle CategoryNotFound
}
catch (FaultException<UnexpectedExceptionDetail> fe)
{
    //handle UnexpectedExceptionDetail
}
catch (FaultException exc)
{
    //stuf
}
catch(Exception general){
    //all other stuff that might blow up
}
RoelF
  • 7,483
  • 5
  • 44
  • 67