我对在WCF REST服务客户端中正确处理故障很感兴趣。当使用任何WebClient、WebRequest或HttpWebRequest时,如:
try
{
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
req.Method = "GET";
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
// ...process...
}
catch (WebException wex)
{
string exMessage = wex.Message;
if (wex.Response != null)
{
using (StreamReader r = new StreamReader(wex.Response.GetResponseStream()))
exMessage = r.ReadToEnd();
// the fault xml is available here, really need to parse? and how?
}
}
我可以在Fiddler中看到,我得到了一个格式良好的XML"Fault"消息(要么是默认的,因为includeExceptionDetailInFaults=true,要么是通过IErrorHandler:ProviderFault的自定义错误)。但是,只抛出了一个500内部错误WebException。
我宁愿在客户端上抛出FaultException,或者至少能够解析Fault。我们没有使用"服务引用",因此没有代理(如果有更好的方法可以为REST WCF客户端做到这一点,请更正我)。有没有一种通用的方法来解析该故障,而不管它的实际类型T(FaultException),甚至是作为起点的特定类型?谢谢
基于degorolls的回答:
public SomeContract ThrowErrorTest()
{
try
{
return TryCatchExtractAndRethrowFaults<SomeContract>(() =>
{
// Call web service using WebClient, HttpWebRequest, etc.
return SomeContract;
});
}
catch (FaultException<CustomFault> fexCustom)
{
Dbg.WriteLine(fexCustom.Message);
}
catch (FaultException fex)
{
Dbg.WriteLine(fex.Message);
}
catch (WebException wex)
{
Dbg.WriteLine(wex.Message);
}
catch (Exception ex)
{
Dbg.WriteLine(ex.Message);
}
return null;
}
static public T TryCatchExtractAndRethrowFaults<T>(Func<T> doWebRequest)
{
try
{
return doWebRequest();
}
catch (WebException wex)
{
FaultException fe = ConvertWebExceptionIntoFault(wex);
if (fe != null)
throw fe;
throw; // not a fault, just re-throw
}
}
static protected FaultException ConvertWebExceptionIntoFault(WebException wex)
{
if (wex.Response == null)
return null;
XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(
wex.Response.GetResponseStream(),
new XmlDictionaryReaderQuotas());
Message msg = Message.CreateMessage(MessageVersion.None, "ParseFaultException", xdr);
// If the start element of the message is "Fault" convert it into a FaultException
//
using (MessageBuffer msgBuffer = msg.CreateBufferedCopy(65536))
using (Message msgCopy = msgBuffer.CreateMessage())
using (XmlDictionaryReader reader = msgCopy.GetReaderAtBodyContents())
if (reader.IsStartElement("Fault"))
{
// Must make a copy for the converter
msg.Close();
msg = msgBuffer.CreateMessage();
return ConvertMessageToFault(msg);
}
return null;
}
static FaultException ConvertMessageToFault(Message msg)
{
EnvelopeVersion ev = msg.Version.Envelope;
var fault = MessageFault.CreateFault(msg, 65536);
if (fault.HasDetail)
{
string faultName = fault.GetReaderAtDetailContents().Name;
switch (faultName)
{
case "ExceptionDetail": // handle the default WCF generated fault
ExceptionDetail exDetail = fault.GetDetail<ExceptionDetail>();
return new FaultException<ExceptionDetail>(exDetail, fault.Reason, fault.Code);
case "CustomFault": // handle custom faults
CustomFault cstmDetail = fault.GetDetail<CustomFault>();
return new FaultException<CustomFault>(cstmDetail, fault.Reason, fault.Code);
default:
throw new Exception("Unrecognized fault detail '" + faultName +
"' while re-constructing fault.");
}
}
return null;
}
故障是SOAP协议的一部分,在REST场景中不可用。我不相信任何WCF基础设施支持您开箱即用的功能。
您可以在WebHttp行为配置中设置FaultException Enabled=true,以获得FaultException而不是500错误。
但是,您也可以这样做(我在一些测试场景中已经这样做了)。此方法依赖于提前了解故障中预期的FaultDetails类型。
bool isFault;
if (message.Version == MessageVersion.None)
{
//Need to determine for ourselves if this is a fault;
using (MessageBuffer buffer = message.CreateBufferedCopy(65536))
{
message.Close();
message = buffer.CreateMessage();
using (Message message2 = buffer.CreateMessage())
{
using (XmlDictionaryReader reader = message2.GetReaderAtBodyContents())
{
isFault = reader.IsStartElement("Fault", "http://schemas.microsoft.com/ws/2005/05/envelope/none");
}
}
}
}
else
{
// For SOAP messages this is done for us
isFault = message.IsFault;
}
if (isFault)
{
var fault = MessageFault.CreateFault(message, 65536);
MyServiceFault detail = null;
if (fault.HasDetail)
{
// The only thing we can possible have as detail is an MyServiceFault
detail = fault.GetDetail<MyServiceFault>();
}
FaultException ex = new FaultException<MyServiceFault>(detail, fault.Reason, fault.Code);
throw ex;
}