WCF方法引发Exception时以空响应调用jQuery成功回调 [英] jQuery success callback called with empty response when WCF method throws an Exception

查看:65
本文介绍了WCF方法引发Exception时以空响应调用jQuery成功回调的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我要把这根头发撕掉,所以请忍受(这是一篇很长的文章).

I'm tearing my hair out over this one, so bear with me (it's a long post).

  • 在ASP.NET兼容模式下具有WCF服务的ASP.NET 3.5
  • 将jQuery与此服务代理一起使用以处理AJAX请求
  • 自定义IErrorHandlerIServiceBehavior实现以捕获异常并提供Fault(已序列化为JSON)
  • 我正在使用Cassini在本地进行测试(我已经看到一些线程谈论在本地调试时出现的问题,但在生产环境中可以正常工作).
  • ASP.NET 3.5 with WCF service in ASP.NET compatibility mode
  • Using jQuery with this service proxy for AJAX requests
  • Custom IErrorHandler and IServiceBehavior implementation to trap exceptions and provide Faults, which are serialized to JSON
  • I'm testing locally using Cassini (I've seen some threads that talk about issues that occur when debugging locally but work fine in a production environment).

我遇到的问题是,每当从WCF服务中引发异常时,都会触发$.ajax调用的成功处理程序.响应为空,状态文本为成功",响应代码为202/已接受.

The issue I'm running into is that whenever an exception is thrown from my WCF service, the success handler of the $.ajax call is fired. The response is empty, the status text is "Success" and the response code is 202/Accepted.

IErrorHandler实现确实得到使用,因为我可以逐步执行它,并观察FaultMessage被创建.最后发生的是success回调引发错误,因为当期望JSON字符串时响应文本为空. error回调从不触发.

The IErrorHandler implementation does get used because I can step through it and watch the FaultMessage get created. What happens in the end is that the success callback throws an error because the response text is empty when it is expecting a JSON string. The error callback never fires.

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

One thing that provided a little insight was removing the enableWebScript option from the endpoint behavior. Two things happened when I did this:

  1. 响应不再包装(即没有{ d: "result" },只有"result").
  2. 触发了error回调,但是响应只是来自IIS的400/Bad请求黄屏死机的HTML,而不是我的序列化错误.
  1. The responses were no longer wrapped (i.e. no { d: "result" }, just "result").
  2. The error callback is fired, but the response is only the HTML for the 400/Bad Request yellow-screen-of-death from IIS, not my serialized fault.

我已经尝试了Google进行的与关键字"jquery ajax asp.net wcf faultcontract json"的随机组合有关的前10条结果中的所有操作,因此,如果您打算使用Google搜索来寻找答案,请不要尝试不用理会.我希望有人曾经遇到过这个问题.

I've tried as many things as show up in the top 10 results or more from Google regarding random combinations of the keywords "jquery ajax asp.net wcf faultcontract json", so if you plan on googling for an answer, don't bother. I'm hoping somebody on SO has run into this issue before.

我最终想要实现的是:

  1. 能够在WCF方法中抛出任何类型的Exception
  2. 使用FaultContact
  3. 捕获ShipmentServiceErrorHandler
  4. 中的异常
  5. 将序列化的ShipmentServiceFault(作为JSON)返回给客户端.
  6. 已调用error回调,以便我可以处理项目4.
  1. Be able to throw any type of Exception in a WCF method
  2. Use a FaultContact
  3. Trap the exceptions in the ShipmentServiceErrorHandler
  4. Return a serialized ShipmentServiceFault (as JSON) to the client.
  5. Have the error callback invoked so I can handle item 4.

可能与以下内容有关:

更新1

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

I examined the output from tracing System.ServiceModel activity, and at one point after calling the UpdateCountry method, an exception is thrown, the message being

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

Server returned an invalid SOAP Fault.

就是这样.一个内部异常抱怨该序列化程序期望一个不同的根元素,但是我无法从中解密出很多其他东西.

and that's it. An inner exception complains about the serializer expecting a different root element, but I can't decipher much else out of it.

更新2

因此,经过一番混乱之后,我有了一些工作要做,尽管这不是我认为理想的方式.这是我所做的:

So with some more messing around, I got something to work, though not the way I would consider ideal. Here's what I did:

  1. 从web.config的端点行为部分中删除了<enableWebScript />选项.
  2. 从服务方法中删除了FaultContract属性.
  3. 实现了WebHttpBehavior的子类(称为ShipmentServiceWebHttpBehavior),并覆盖了AddServerErrorHandlers函数以添加ShipmentServiceErrorHandler.
  4. 更改了ShipmentServiceErrorHandlerElement以返回类型为ShipmentServiceWebHttpBehavior的实例,而不是错误处理程序本身.
  5. <errorHandler />行从web.config的服务行为部分移动到了端点行为部分.
  1. Removed the <enableWebScript /> option from the endpoint behavior section of the web.config.
  2. Removed the FaultContract attribute from the service method.
  3. Implemented a subclass of WebHttpBehavior (called ShipmentServiceWebHttpBehavior) and overrode the AddServerErrorHandlers function to add the ShipmentServiceErrorHandler.
  4. Changed the ShipmentServiceErrorHandlerElement to return an instance of type of ShipmentServiceWebHttpBehavior instead of the error handler itself.
  5. Moved the <errorHandler /> line from the service behavior section of the web.config to the endpoint behavior section.

这是不理想的,因为现在WCF忽略了我想要在服务方法中使用的BodyStyle = WebMessageBodyStyle.WrappedRequest(尽管现在我可以将其完全省略).我还不得不更改JS服务代理中的某些代码,因为它正在寻找响应中的包装器({ d: ... })对象.

It's not ideal because now WCF ignores the BodyStyle = WebMessageBodyStyle.WrappedRequest I want on my service methods (though I can now omit it altogether). I also had to change some of the code in the JS service proxy because it was looking for a wrapper ({ d: ... }) object on the responses.

这里是所有相关代码(ShipmentServiceFault对象很容易解释).

Here is all of the relevant code (the ShipmentServiceFault object is pretty self explanatory).

我的服务简直太简单了(截短的版本):

My service is dead simple (truncated version):

[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方法始于:

The JavaScript

Calling the WCF method begins with:

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

我前面提到的服务代理处理了很多事情,但从根本上讲,我们到达了这里:

The service proxy I mentioned earlier takes care of a lot of things, but at the core, we get to here:

$.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) {                

    }
});

配置

我觉得问题可能就在这里,但是我已经尝试了在网上所有有示例的地方进行的几乎所有设置组合.

Configuration

I feel like the problems may lie here, but I've tried just about every combination of settings from everywhere I can find on the 'net that has an example.

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

注释

我注意到这个问题越来越受欢迎.我没有找到了解决此问题的方法,希望在有时间的时候提供一个答案.敬请期待!

I noticed this questions is getting a few favorites. I did find the solution to this issue and I hope to provide an answer when I find some time. Stay tuned!

推荐答案

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

I'm not familiar with ASP or WCF, but I am quite familiar with jQuery. The one thing that sticks out in my mind about your question is that your service is returning 202 Success when an exception is thrown. jQuery chooses which callback to call (success or error) based on the HTTP status code that is returned from the server. 202 is considered a successful response, and therefor jQuery will call success. If you want to have jQuery call the error callback, you need to make your service return a 40x or 50x status code. Consult http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for a list of HTTP status codes.

这篇关于WCF方法引发Exception时以空响应调用jQuery成功回调的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆