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

Posted

技术标签:

【中文标题】当 WCF 方法抛出异常时,使用空响应调用 jQuery 成功回调【英文标题】:jQuery success callback called with empty response when WCF method throws an Exception 【发布时间】:2011-05-20 16:36:03 【问题描述】:

我正在为此撕毁我的头发,所以请耐心等待(这是一篇很长的帖子)。

基本信息

在 ASP.NET 兼容模式下带有 WCF 服务的 ASP.NET 3.5 使用 jQuery 和 this service proxy 处理 AJAX 请求 自定义IErrorHandlerIServiceBehavior 实现以捕获异常并提供Faults,将其序列化为JSON 我正在使用 Cassini 进行本地测试(我看到一些线程讨论了在本地调试时出现的问题,但在生产环境中工作正常)。

我遇到的问题是,每当我的 WCF 服务抛出异常时,$.ajax 调用的 success 处理程序就会被触发。响应为空,状态文本为“Success”,响应代码为 202/Accepted。

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

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

    回复不再被包装(即没有 d: "result" ,只有"result")。 error 回调被触发,但响应只是来自 IIS 的 400/Bad Request Yellow-screen-of-death 的 html,而不是我的序列化错误。

我已经尝试了与 Google 排名前 10 名或更多的关于关键字“jquery ajax asp.net wcf faultcontract json”的随机组合的东西一样多的东西,所以如果你打算在谷歌上搜索答案,请不要不打扰。我希望 SO 上的某个人之前遇到过这个问题。

最终我想要实现的是:

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

可能与:

WCF IErrorHandler Extension not returning specified Fault

更新 1

我检查了跟踪 System.ServiceModel 活动的输出,在调用 UpdateCountry 方法后的某一时刻,抛出了一个异常,消息是

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

就是这样。一个内部异常抱怨序列化程序需要不同的根元素,但我无法从中解读出更多其他内容。


更新 2

因此,在进行了一些杂乱无章的事情后,我得到了一些工作,尽管不是我认为理想的方式。这是我所做的:

    从 web.config 的端点行为部分中删除了 <enableWebScript /> 选项。 从服务方法中删除了FaultContract 属性。 实现了WebHttpBehavior 的子类(称为ShipmentServiceWebHttpBehavior)并覆盖AddServerErrorHandlers 函数以添加ShipmentServiceErrorHandler。 将ShipmentServiceErrorHandlerElement 更改为返回ShipmentServiceWebHttpBehavior 类型的实例,而不是错误处理程序本身。 将 <errorHandler /> 行从 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)
    


错误处理

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<ServiceEndpoint> 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)                 

    
);

配置

我觉得问题可能出在此处,但我已经尝试了几乎所有我可以在有示例的网络上找到的所有设置组合。

<system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <behaviors>
        <endpointBehaviors>
            <behavior name="Removed.ShipmentServiceAspNetAjaxBehavior">
                <webHttp />
                <enableWebScript />
            </behavior>
        </endpointBehaviors>
        <serviceBehaviors>
            <behavior name="Removed.ShipmentServiceServiceBehavior">
                <serviceMetadata httpGetEnabled="true"/>
                <serviceDebug includeExceptionDetailInFaults="false"/>
                <errorHandler />
            </behavior>
        </serviceBehaviors>
    </behaviors>
    <services>
        <service name="ShipmentService" behaviorConfiguration="Removed.ShipmentServiceServiceBehavior">
            <endpoint address="" 
                behaviorConfiguration="Removed.ShipmentServiceAspNetAjaxBehavior" 
                binding="webHttpBinding" 
                contract="ShipmentService" />
        </service>
    </services>
    <extensions>
        <behaviorExtensions>
            <add name="errorHandler" type="Removed.Services.ShipmentServiceErrorHandlerElement, Removed, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
        </behaviorExtensions>
    </extensions>
</system.serviceModel>

备注

我注意到这个问题得到了一些人的喜爱。我确实找到了这个问题的解决方案,希望有时间能提供答案。敬请期待!

【问题讨论】:

【参考方案1】:

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

【讨论】:

这就是答案;如果您捕获到异常或如果您有错误处理程序文档,ASP.NET 不会返回 HTTP 错误代码,您必须自己在代码中修改 HTTP 响应标头。【参考方案2】:

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

以下是我所做的工作和我们的解决方案的简要总结:

我正在发布到我们从经典 ASP 页面托管的 WCF 服务的 REST 实现。我发现我必须将输入设置为流并从中读取,完成后处理流。我相信正是在这一点上,我收到了带有“成功”文本的 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
        

                    
    

希望这对您有所帮助,也许可以尝试使用流,看看效果如何。

【讨论】:

我很欣赏这篇文章,尽管将参数传递给服务方法没有任何问题。我正在尝试使用 FaultException&lt;T&gt; 并将其序列化为 JSON;相反,由于内部服务器异常,它什么也不做,在 202/Accepted 之后终止响应。 没问题,这对我来说充其量只是一个远射,因为症状几乎相同【参考方案3】:

你看过JSON.NET吗?我用它来将 c# 中的对象转换为 JSON 友好的字符串,然后通过网络将其传递回我的客户端,在那里我将其解析为 JSON 对象。最后我摆脱了它并去JSON2进行字符串化。这是我使用的 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([]);
        
    );

【讨论】:

【参考方案4】:

这是另一个镜头。我将保留我最初的尝试,以防该解决方案对其他人有所帮助。

要触发 $.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); 
     

希望我的第二次尝试对你更有用!

【讨论】:

如果您通读代码,您会看到我在帖子的错误处理部分传递了HttpStatusCodeInternalServerErrorthis.ApplyHttpResponseSettings(ref fault, System.Net.HttpStatusCode.InternalServerError, faultDetail.Reason);。无论如何,请查看我的帖子以了解更新 2 的一些进展。【参考方案5】:

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

throw new WebFaultException(HttpStatusCode.BadRequest);

【讨论】:

以上是关于当 WCF 方法抛出异常时,使用空响应调用 jQuery 成功回调的主要内容,如果未能解决你的问题,请参考以下文章

学会WCF之试错法——客户端调用基础

规避空指针异常

将输入流转换为 JSONArrary 时出现空异常

在WCF中,自定义Authentication,Validate方法,抛出错误异常时如何在不停止服务的情况下处理异常?

GetRequestStream 随机抛出 Timeout 异常

调用 WCF 服务时如何在 WPF 中捕获异常