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