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

Cᴏʀ*_*ᴏʀʏ 16 wcf jquery json asp.net-3.5

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

基础信息

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

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

IErrorHandler实现不习惯,因为我可以逐步通过它,观看FaultMessage获得创建.最后发生的是success回调引发错误,因为响应文本在期望JSON字符串时为空.该error回调永远不会触发.

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

  1. 答案不再包裹(即不{ d: "result" },只是"result").
  2. error回调被触发,但响应只是从IIS,不是我的错连载400 /错误的请求黄色屏幕的死亡的HTML.

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

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

  1. 能够Exception在WCF方法中抛出任何类型
  2. 用一个 FaultContact
  3. 陷阱中的异常 ShipmentServiceErrorHandler
  4. 将序列化ShipmentServiceFault(作为JSON)返回给客户端.
  5. error调用,所以我可以处理项目4的回调.

可能与:


更新1

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

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

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


更新2

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

  1. <enableWebScript />从web.config的端点行为部分中删除了该选项.
  2. FaultContract从服务方法中删除了该属性.
  3. 实现WebHttpBehavior(调用ShipmentServiceWebHttpBehavior)的子类并覆盖AddServerErrorHandlers函数以添加ShipmentServiceErrorHandler.
  4. 更改ShipmentServiceErrorHandlerElement为返回类型的实例ShipmentServiceWebHttpBehavior而不是错误处理程序本身.
  5. 将该<errorHandler />行从web.config的服务行为部分移动到端点行为部分.

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


这是所有相关代码(该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)
    }

}
Run Code Online (Sandbox Code Playgroud)

错误处理

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
}
Run Code Online (Sandbox Code Playgroud)

JavaScript

调用WCF方法始于:

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

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

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

    }
});
Run Code Online (Sandbox Code Playgroud)

组态

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

<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>
Run Code Online (Sandbox Code Playgroud)

笔记

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

Jon*_*n P 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
        {

        }            
    }
Run Code Online (Sandbox Code Playgroud)

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