WCFメソッドが例外をスローしたときに空の応答で呼び出されたjQueryの成功コールバック



json asp.net-3.5 (4)

私はこの上に私の髪を裂いているので、私と一緒に(それは長いポストです)クマ。

基本情報

  • ASP.NET互換モードのWCFサービスを使用するASP.NET 3.5
  • このサービスプロキシでjQueryを使用してAJAX要求を行う
  • カスタムIErrorHandlerおよびIServiceBehavior実装は、例外をトラップしてエラーを提供し、JSONにシリアル化されています
  • 私はCassiniを使ってローカルでテストしています(私はローカルでデバッグするときに発生する問題について話すスレッドをいくつか見たことがありますが、実稼働環境でうまく動作します)。

私が実行している問題は、例外がWCFサービスからスローされるたびに、 $.ajax呼び出しの成功ハンドラが起動されることです。 応答は空で、ステータス・テキストは "Success"で、応答コードは202 / Acceptedです。

IErrorHandler実装は、私がそれを踏んでFaultMessageが作成されるのを見ることができるので使用されます。 最後に何が起こるかは、JSON文字列が必要なときに応答テキストが空であるため、 successコールバックがエラーをスローするということです。 errorコールバックは発生しません。

ちょっとした洞察を提供したことの1つは、エンドポイントの動作からenableWebScriptオプションを削除することenableWebScript 。 私がこれをしたときに2つのことが起こった:

  1. 応答はもはやラップされませんでした(つまり、 { d: "result" } 、単に"result" )。
  2. errorコールバックはerrorますが、レスポンスは、シリアル化されたフォールトではなく、IISからの黄色の画面の400 / Bad RequestのHTMLのみです。

私はキーワード "jquery ajax asp.net wcf faultcontract json"のランダムな組み合わせに関してGoogleからトップ10の結果またはそれ以上のものが表示されるように多くのことを試しました。したがって、グーグルで答えを計画する場合は気にしないでください。 私はSOの誰かが以前この問題に遭遇したことを願っています。

最終的に私が達成したいことは次のとおりです。

  1. WCFメソッドで任意のタイプのExceptionをスローできるようにする
  2. FaultContact使用する
  3. ShipmentServiceErrorHandlerの例外をトラップする
  4. シリアライズされたShipmentServiceFault (JSONとして)をクライアントに返します。
  5. errorコールバックを呼び出して、アイテム4を処理できるようにしてerror

おそらく関連する:

アップデート1

私は、System.ServiceModelアクティビティをトレースして出力を調べ、UpdateCountryメソッドを呼び出した後のある時点で、例外がスローされました。

サーバーが無効なSOAPフォールトを返しました。

以上です。 内部例外は、シリアライザが別のルート要素を期待していると文句を言いますが、それ以外の部分を多く解読することはできません。

アップデート2

だから私は理想的な方法ではありませんが、何か面倒なことをすると、私は働くことがあります。 ここに私がしたことがあります:

  1. web.configのエンドポイント動作セクションから<enableWebScript />オプションを削除しました。
  2. サービスメソッドからFaultContract属性を削除しました。
  3. WebHttpBehaviorShipmentServiceWebHttpBehavior )のサブクラスを実装し、 AddServerErrorHandlers関数をAddServerErrorHandlersしてShipmentServiceErrorHandlerを追加しました。
  4. ShipmentServiceWebHttpBehaviorを、エラーハンドラ自体ではなくShipmentServiceWebHttpBehaviorの型のインスタンスを返すように変更しました。
  5. <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>

ノート

私はこの質問がいくつかのお気に入りを得ていることに気づいた。 私この問題の解決策を見つけました。私はしばらく時間をとって答えを出すことを願っています。 乞うご期待!


Answer #1

あなたはJSON.NETを見ましたか? 私はそれをJSONのフレンドリーな文字列に変換して、クライアントに渡してJSONオブジェクトにパースしました。 最終的に私はそれを取り除き、 JSON2ためにJSON2に行きました。 ここで私が使用する私のアヤックスコールです:

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

Answer #2

ここに別のショットがあります。 その解決策が他の人を助けてくれるなら、私は元の試みを残すだろう。

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

Heresは私の2番目のattmptがあなたにとってもっと有用であることを望んでいます!


Answer #3

私はWCFと同様の問題を抱えていましたが、私のソリューションではMVCとWCFを統合するためにASP.NET互換性を使用していました。 私がやることは、WebFaultExceptionをスローし、受信側(Javaまたは他の.NETクライアントのいずれか)で応答のステータスを確認することです。 WebOperationContext.Currentがnullでない場合、カスタムエラーが発生する可能性があります。 あなたはおそらくこれをすでに認識していますが、私はそこに投げ捨てると思っていました。

throw new WebFaultException(HttpStatusCode.BadRequest);

Answer #4

私は別のシナリオで同じ症状があったので、これは役立つかもしれません。

ここで私がやっていたことと私たちの解決策の概要を簡単に説明します:

私は、古典的な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
        {

        }            
    }

うまくいけば、これはあなたに役立つかもしれない、おそらくストリームを使用してみて、それがどうなるか見てみてください。