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 (WebExcep开发者_Go百科tion 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?
The answer is to implement an IErrorHandler and supporting behavior
I found this excellent post by iainjmitchell
http://iainjmitchell.com/blog/?p=142
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
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.
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.
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
}
//由于调用 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);
}
精彩评论