Подтвердить что ты не робот

JQuery callback с вызовом с пустым ответом, когда метод WCF выдает исключение

Я отрываю свои волосы от этого, так что медведь со мной (это длинный пост).

Основная информация

  • ASP.NET 3.5 с сервисом WCF в режиме совместимости ASP.NET
  • Использование jQuery с этот служебный прокси для запросов AJAX
  • Пользовательские IErrorHandler и IServiceBehavior реализации для исключения прерываний и предоставления ошибок, которые сериализуются в JSON
  • Я тестирую локально с помощью Cassini (я видел некоторые потоки, которые рассказывают о проблемах, возникающих при локальном отладке, но отлично работают в производственной среде).

Проблема, с которой я сталкиваюсь, заключается в том, что всякий раз, когда исключение вызывается из моей службы WCF, выполняется обработчик success вызова $.ajax. Ответ пуст, текст состояния - "Успех", а код ответа - 202/Принято.

Реализация IErrorHandler используется, потому что я могу пройти через нее и посмотреть, как создается FaultMessage. В конце концов происходит то, что обратный вызов success вызывает ошибку, потому что текст ответа пуст, когда он ожидает строку JSON. Обратный вызов error никогда не срабатывает.

Одна вещь, которая дала небольшую информацию, заключалась в удалении опции enableWebScript из поведения конечной точки. Когда я это сделал, произошли две вещи:

  • Ответы больше не были завернуты (т.е. нет { d: "result" }, просто "result").
  • Обратный вызов error запускается, но ответ - это только HTML для желтого экрана 400/Bad Request из-за смерти от IIS, а не моей сериализованной ошибки.

Я пробовал столько же, сколько обнаружил в 10 лучших результатах или больше от Google в отношении случайных комбинаций ключевых слов "jquery ajax asp.net wcf faultcontract json", поэтому, если вы планируете искать ответы на Google, Не беспокойтесь. Я надеюсь, что кто-то из SO столкнулся с этим вопросом раньше.

В конечном итоге я хочу достичь:

  • Уметь бросать любой тип Exception в WCF-метод
  • Используйте FaultContact
  • Ловушка исключений в ShipmentServiceErrorHandler
  • Возвращает сериализованный ShipmentServiceFault (как JSON) клиенту.
  • Вызов обратного вызова error, чтобы я мог обрабатывать элемент 4.

Возможно, связано с:


Обновление 1

Я просмотрел результат отслеживания активности System.ServiceModel и в какой-то момент после вызова метода UpdateCountry генерируется исключение, сообщение

Сервер вернул недопустимую ошибку SOAP.

и что он. Внутреннее исключение жалуется на сериализатор, ожидающий другого корневого элемента, но я не могу его расшифровать.


Обновление 2

Итак, с некоторыми более беспорядочными, у меня есть кое-что, чтобы работать, но не так, как я бы считал идеальным. Вот что я сделал:

  • Удалена опция <enableWebScript /> из раздела поведения конечной точки в файле web.config.
  • Удален атрибут 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>

Примечания

Я заметил, что эти вопросы получают несколько фаворитов. Я нашел решение этой проблемы, и я надеюсь дать ответ, когда найду какое-то время. Оставайтесь с нами!

4b9b3361

Ответ 1

Я не знаком с ASP или WCF, но я хорошо знаком с jQuery. Единственное, что вызывает у меня в голове вопрос о том, что ваша служба возвращает 202 Success, когда генерируется исключение. jQuery выбирает, какой callback для вызова (success или error) на основе кода состояния HTTP, который возвращается с сервера. 202 считается успешным ответом, и поэтому jQuery будет вызывать success. Если вы хотите, чтобы jQuery вызывал обратный вызов error, вам нужно вернуть свою службу в код состояния 40x или 50x. Обратитесь http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html к списку кодов состояния HTTP.

Ответ 2

У меня были те же симптомы с другим сценарием, чтобы это могло или не помогло.

Вот краткий обзор того, что я делал, и наше решение:

Я отправлял в REST-реализацию службы WCF, которую мы размещаем на классической странице 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
        {

        }            
    }

Надеюсь, это поможет вам, возможно, попробуйте использовать поток и посмотрите, как это происходит.

Ответ 3

Вы посмотрели JSON.NET? Я использовал его для преобразования объектов в С# в 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); 
    } 

Heres надеется, что мой второй attmpt более полезен для вас!

Ответ 5

У меня была аналогичная проблема с WCF и с использованием совместимости ASP.NET, когда я интегрировал MVC и WCF в свое решение. То, что я хотел бы сделать, это бросить WebFaultException, а затем проверить статус ответа на получающей стороне (либо Java, либо другой .NET-клиент). Ваша пользовательская ошибка может затем бросить это, если WebOperationContext.Current не является нулевым. Возможно, вы уже знаете об этом, но просто подумали, что я его выброшу.

throw new WebFaultException(HttpStatusCode.BadRequest);