当WCF方法抛出exception时,使用空响应调用jQuery成功回调

我把头发撕成了这个,所以忍受我(这是一个很长的post)。

基础信息

  • ASP.NET 3.5与ASP.NET兼容模式下的WCF服务
  • 将jQuery与此服务代理一起用于AJAX请求
  • 自定义IErrorHandlerIServiceBehavior实现来捕获exception并提供故障,这些故障被序列化为JSON
  • 我正在使用Cassini进行本地测试(我看过一些线程,讨论在本地调试时出现的问题,但在生产环境中工作正常)。

我遇到的问题是,无论何时从我的WCF服务抛出exception,都会触发$.ajax调用的成功处理程序。 响应为空,状态文本为“成功”,响应代码为202 /已接受。

IErrorHandler实现确实被使用,因为我可以单步执行它并观察创建的FaultMessage。 最后发生的事情是success回调会引发错误,因为响应文本在期望JSON字符串时为空。 error回调永远不会触发。

提供一点洞察力的一件事是从端点行为中删除enableWebScript选项。 我这样做时发生了两件事:

  1. 回复不再被包裹(即没有{ d: "result" } ,只是"result" )。
  2. error回调被触发,但响应只是来自IIS的400 / Bad Request黄色死亡屏幕的HTML,而不是我的序列化故障。

我尝试过关于关键字“jquery ajax asp.net wcf faultcontract json”的随机组合的前10个或更多结果中显示的内容,所以如果你打算谷歌搜索答案,请不要打扰。 我希望SO上的某个人之前遇到过这个问题。

最终我想要实现的目标是:

  1. 能够在WCF方法中抛出任何类型的Exception
  2. 使用FaultContact
  3. 捕获ShipmentServiceErrorHandler的exception
  4. 将序列化的ShipmentServiceFault (作为JSON)返回给客户端。
  5. 调用error回调,以便我可以处理第4项。

可能与以下内容有关:

  • WCF IErrorHandler扩展未返回指定的Fault

更新1

我检查了跟踪System.ServiceModel活动的输出,并在调用UpdateCountry方法后的一个点上抛出了一个exception,消息是

服务器返回了无效的SOAP错误。

就是这样。 一个内部exception抱怨序列化程序期望一个不同的根元素,但我不能破译其他很多东西。


更新2

因此,随着更多的麻烦,我得到了一些工作,虽然不是我认为理想的方式。 这是我做的:

  1. 从web.config的端点行为部分中删除了选项。
  2. 从服务方法中删除了FaultContract属性。
  3. 实现了WebHttpBehavior的子类(称为ShipmentServiceWebHttpBehavior )并覆盖了AddServerErrorHandlers函数以添加ShipmentServiceErrorHandler
  4. 更改了ShipmentServiceErrorHandlerElement以返回ShipmentServiceErrorHandlerElement类型的实例,而不是error handling程序本身。
  5. 行从web.config的服务行为部分移动到端点行为部分。

它并不理想,因为现在WCF忽略了我想要的服务方法BodyStyle = WebMessageBodyStyle.WrappedRequest (尽管我现在可以完全省略它)。 我还必须更改JS服务代理中的一些代码,因为它在响应中寻找包装器( { d: ... } )对象。


这是所有相关代码( ShipmentServiceFault对象非常自我解释)。

服务

我的服务很简单(截断版):

 [ServiceContract(Namespace = "http://removed")] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class ShipmentService { [OperationContract] [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)] [FaultContract(typeof(ShipmentServiceFault))] public string UpdateCountry(Country country) { var checkName = (country.Name ?? string.Empty).Trim(); if (string.IsNullOrEmpty(checkName)) throw new ShipmentServiceException("Country name cannot be empty."); // Removed: try updating country in repository (works fine) return someHtml; // new country information HTML (works fine) } } 

error handling

IErrorHandler, IServiceBehavior实现如下:

 public class ShipmentServiceErrorHandlerElement : BehaviorExtensionElement { protected override object CreateBehavior() { return new ShipmentServiceErrorHandler(); } public override Type BehaviorType { get { return typeof(ShipmentServiceErrorHandler); } } } public class ShipmentServiceErrorHandler : IErrorHandler, IServiceBehavior { #region IErrorHandler Members public bool HandleError(Exception error) { // We'll handle the error, we don't need it to propagate. return true; } public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault) { if (!(error is FaultException)) { ShipmentServiceFault faultDetail = new ShipmentServiceFault { Reason = error.Message, FaultType = error.GetType().Name }; fault = Message.CreateMessage(version, "", faultDetail, new DataContractJsonSerializer(faultDetail.GetType())); this.ApplyJsonSettings(ref fault); this.ApplyHttpResponseSettings(ref fault, System.Net.HttpStatusCode.InternalServerError, faultDetail.Reason); } } #endregion #region JSON Exception Handling protected virtual void ApplyJsonSettings(ref Message fault) { // Use JSON encoding var jsonFormatting = new WebBodyFormatMessageProperty(WebContentFormat.Json); fault.Properties.Add(WebBodyFormatMessageProperty.Name, jsonFormatting); } protected virtual void ApplyHttpResponseSettings(ref Message fault, System.Net.HttpStatusCode statusCode, string statusDescription) { var httpResponse = new HttpResponseMessageProperty() { StatusCode = statusCode, StatusDescription = statusDescription }; httpResponse.Headers[HttpResponseHeader.ContentType] = "application/json"; httpResponse.Headers["jsonerror"] = "true"; fault.Properties.Add(HttpResponseMessageProperty.Name, httpResponse); } #endregion #region IServiceBehavior Members public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { // Do nothing } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { IErrorHandler errorHandler = new ShipmentServiceErrorHandler(); foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers) { ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher; if (channelDispatcher != null) { channelDispatcher.ErrorHandlers.Add(errorHandler); } } } public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { // Do nothing } #endregion } 

JavaScript

调用WCF方法始于:

  function SaveCountry() { var data = $('#uxCountryEdit :input').serializeBoundControls(); ShipmentServiceProxy.invoke('UpdateCountry', { country: data }, function(html) { $('#uxCountryGridResponse').html(html); }, onPageError); } 

我之前提到的服务代理处理了很多事情,但核心是我们到达这里:

 $.ajax({ url: url, data: json, type: "POST", processData: false, contentType: "application/json", timeout: 10000, dataType: "text", // not "json" we'll parse success: function(response, textStatus, xhr) { }, error: function(xhr, status) { } }); 

组态

我觉得问题可能就在这里,但我已经尝试过几乎所有我可以在网上找到的设置组合。

                             

笔记

我注意到这个问题得到了一些collections。 我确实找到了这个问题的解决方案,我希望在找到一些时间后给出答案。 敬请关注!

我不熟悉ASP或WCF,但我对jQuery非常熟悉。 在我的脑海中突出你关于你的问题的一件事是你的服务在抛出exception时返回202 Success 。 jQuery根据从服务器返回的HTTP状态代码选择要调用的回调( successerror )。 202被认为是成功的响应,因此jQuery将称之为success 。 如果你想让jQuery调用error回调,你需要让你的服务返回40x50x状态代码。 有关HTTP状态代码的列表,请参阅http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 。

我有不同的情况相同的症状,所以这可能会或可能没有帮助。

以下是我正在做的事情和解决方案的简要总结:

我发布了一个WCF服务的REST实现,我们从一个经典的ASP页面托管。 我发现我必须将输入设置为流并从中读取,完成后处理流。 我相信,正是在这一点上,正如你所描述的那样,我正在收到带有“成功”文本的202回复。 我发现通过不处理流我得到了我期待的错误条件的响应。

以下是最终代码的摘要:

 [WebHelp(Comment="Expects the following parameters in the post data:title ...etc")] public int SaveBook(Stream stream) { NameValueCollection qString; StreamReader sr = null; string s; try { /************************************************************************************** * DO NOT CALL DISPOSE ON THE STREAMREADER OR STREAM * * THIS WILL CAUSE THE ERROR HANDLER TO RETURN A PAGE STATUS OF 202 WITH NO CONTENT * * IF THERE IS AN ERROR * * ***********************************************************************************/ sr = new StreamReader(stream); s = sr.ReadToEnd(); qString = HttpUtility.ParseQueryString(s); string title = qString["title"]; //Do what we need //Then Return something int retRecieptNum = UtilitiesController.SubmitClientEntryRequest(entryReq); return retRecieptNum; } catch (Exception ex) { throw new WebProtocolException(System.Net.HttpStatusCode.Forbidden, ex.Message, this.GetExceptionElement(true, "BookRequest", ex.Message), false, ex); } finally { } } 

希望这对你有所帮助,也许尝试使用一个流,看看它是怎么回事。

你看过JSON.NET了吗? 我正在使用它将c#中的对象转换为JSON友好字符串,然后将其传递回我的客户端,在那里我将其解析为JSON对象。 最后我摆脱了它并转到JSON2进行stringify。 这是我使用的ajax调用:

 function callScriptMethod(url, jsonObject, callback, async) { callback = callback || function () { }; async = (async == null || async); $.ajax({ type: 'POST', contentType: 'application/json; charset=utf-8', url: url, data: JSON.stringify(jsonObject), dataType: 'json', async: async, success: function (jsonResult) { if ('d' in jsonResult) callback(jsonResult.d); else callback(jsonResult); }, error: function () { alert("Error calling '" + url + "' " + JSON.stringify(jsonObject)); callback([]); } }); } 

这是另一个镜头。 我将离开原来的尝试,因为该解决方案可以帮助其他人。

要触发$ .ajax调用的错误条件,您的响应中将需要一个错误代码

  protected virtual void ApplyHttpResponseSettings(ref Message fault, System.Net.HttpStatusCode statusCode, string statusDescription) { var httpResponse = new HttpResponseMessageProperty() { //I Think this could be your problem, if this is not an error code //The error condition will not fire //StatusCode = statusCode, //StatusDescription = statusDescription //Try forcing an error code StatusCode = System.Net.HttpStatusCode.InternalServerError; }; httpResponse.Headers[HttpResponseHeader.ContentType] = "application/json"; httpResponse.Headers["jsonerror"] = "true"; fault.Properties.Add(HttpResponseMessageProperty.Name, httpResponse); } 

希望我的第二个吸引力对你更有用!

我有一个类似的问题与WCF和使用ASP.NET兼容性,因为我在我的解决方案中集成了MVC和WCF。 我要做的是抛出WebFaultException然后检查接收端(java或其他.NET客户端)的响应状态。 如果WebOperationContext.Current不为null,则您的自定义错误可能会抛出该错误。 你可能已经意识到这一点,但只是想我会把它扔出去。

 throw new WebFaultException(HttpStatusCode.BadRequest);