当WCF方法抛出exception时,使用空响应调用jQuery成功回调
我把头发撕成了这个,所以忍受我(这是一个很长的post)。
基础信息
- ASP.NET 3.5与ASP.NET兼容模式下的WCF服务
- 将jQuery与此服务代理一起用于AJAX请求
- 自定义
IErrorHandler
和IServiceBehavior
实现来捕获exception并提供故障,这些故障被序列化为JSON - 我正在使用Cassini进行本地测试(我看过一些线程,讨论在本地调试时出现的问题,但在生产环境中工作正常)。
我遇到的问题是,无论何时从我的WCF服务抛出exception,都会触发$.ajax
调用的成功处理程序。 响应为空,状态文本为“成功”,响应代码为202 /已接受。
IErrorHandler
实现确实被使用,因为我可以单步执行它并观察创建的FaultMessage。 最后发生的事情是success
回调会引发错误,因为响应文本在期望JSON字符串时为空。 error
回调永远不会触发。
提供一点洞察力的一件事是从端点行为中删除enableWebScript
选项。 我这样做时发生了两件事:
- 回复不再被包裹(即没有
{ d: "result" }
,只是"result"
)。 -
error
回调被触发,但响应只是来自IIS的400 / Bad Request黄色死亡屏幕的HTML,而不是我的序列化故障。
我尝试过关于关键字“jquery ajax asp.net wcf faultcontract json”的随机组合的前10个或更多结果中显示的内容,所以如果你打算谷歌搜索答案,请不要打扰。 我希望SO上的某个人之前遇到过这个问题。
最终我想要实现的目标是:
- 能够在WCF方法中抛出任何类型的
Exception
- 使用
FaultContact
- 捕获
ShipmentServiceErrorHandler
的exception - 将序列化的
ShipmentServiceFault
(作为JSON)返回给客户端。 - 调用
error
回调,以便我可以处理第4项。
可能与以下内容有关:
- WCF IErrorHandler扩展未返回指定的Fault
更新1
我检查了跟踪System.ServiceModel活动的输出,并在调用UpdateCountry方法后的一个点上抛出了一个exception,消息是
服务器返回了无效的SOAP错误。
就是这样。 一个内部exception抱怨序列化程序期望一个不同的根元素,但我不能破译其他很多东西。
更新2
因此,随着更多的麻烦,我得到了一些工作,虽然不是我认为理想的方式。 这是我做的:
- 从web.config的端点行为部分中删除了
选项。
- 从服务方法中删除了
FaultContract
属性。 - 实现了
WebHttpBehavior
的子类(称为ShipmentServiceWebHttpBehavior
)并覆盖了AddServerErrorHandlers
函数以添加ShipmentServiceErrorHandler
。 - 更改了
ShipmentServiceErrorHandlerElement
以返回ShipmentServiceErrorHandlerElement
类型的实例,而不是error handling程序本身。 - 将
行从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状态代码选择要调用的回调( success
或error
)。 202
被认为是成功的响应,因此jQuery将称之为success
。 如果你想让jQuery调用error
回调,你需要让你的服务返回40x
或50x
状态代码。 有关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);