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

时间:2010-12-05 22:47:04

标签: jquery wcf json asp.net-3.5

我正在把头发撕成一片,所以忍受我(这是一个很长的帖子)。

基本信息

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

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

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

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

  1. 不再包含回复(即没有{ d: "result" },只有"result")。
  2. error回调被触发,但响应只是来自IIS的400 / Bad Request黄色死亡屏幕的HTML,而不是我的序列化故障。
  3. 我尝试过关于关键字“jquery ajax asp.net wcf faultcontract json”的随机组合的前10个或更多结果中显示的内容,所以如果你打算用谷歌搜索答案,那就不要不好意思。我希望SO之前有人遇到过这个问题。

    最终我想要达到的目标是:

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

      有关

      更新1

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

        

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

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


      更新2

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

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

        备注

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

5 个答案:

答案 0 :(得分:3)

答案 1 :(得分:1)

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

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

我发布了一个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
        {

        }            
    }

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

答案 2 :(得分:1)

你看过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([]);
        }
    });
}

答案 3 :(得分:0)

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

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

希望我的第二个诱惑对你更有用!

答案 4 :(得分:0)

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

throw new WebFaultException(HttpStatusCode.BadRequest);