Я отрываю свои волосы от этого, так что медведь со мной (это длинный пост).
Основная информация
- 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>
Примечания
Я заметил, что эти вопросы получают несколько фаворитов. Я нашел решение этой проблемы, и я надеюсь дать ответ, когда найду какое-то время. Оставайтесь с нами!