Возврат сведений об ошибке из службы WCF с поддержкой AJAX
Короткая версия: существует ли / каков предлагаемый способ возврата сведений об ошибке клиенту, когда в службе WCF с поддержкой AJAX генерируется исключение (кроме простого открытия ворот и отправки всех сведений об исключении)?
Длинная версия:
У меня есть относительно простая служба WCF с поддержкой AJAX, которую я вызываю с клиента, используя прокси-сервер службы по умолчанию. Ниже приведены фрагменты кода, но я не верю, что в самом коде что-то не так.
Моя проблема в том, что если я выбрасываю исключение в сервисе, объект ошибки, возвращаемый клиенту, всегда является общим:
{
"ExceptionDetail":null,
"ExceptionType":null,
"Message":"The server was unable to process the request..."
"StackTrace":null
}
В идеале я хотел бы отображать различные сообщения об ошибках на клиенте в зависимости от того, что пошло не так.
Один из вариантов - разрешить исключения в ошибках WCF, что дало бы мне полную трассировку стека и все такое, но я ценю проблемы безопасности с этим, и это на самом деле намного больше информации, чем мне нужно. Я мог бы обойтись только возможностью отправить обратно строку, описывающую проблему или что-то еще, но я не вижу способа сделать это.
Мой сервисный код:
[ServiceContract(Namespace = "MyNamespace")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyService
{
[OperationContract]
public void DoStuff(string param1, string etc)
{
//Do some stuff that maybe causes an exception
}
}
На клиенте:
MyNamespace.MyService.DoStuff(
param1,
etc,
function() { alert("success"); },
HandleError);
где "HandleError" - это просто общий метод обработки ошибок, который отображает подробности об ошибке.
5 ответов
РЕДАКТИРОВАТЬ: Обновил пост с соответствующим пользовательским обработчиком ошибок JSON
Быстрый, но не предпочтительный способ.
<serviceDebug includeExceptionDetailInFaults="true"/>
В вашем сервисе поведение предоставит вам все необходимые детали.
Хороший путь
Все исключения из приложения преобразуются в JsonError
и сериализовано с использованием DataContractJsonSerializer
, Exception.Message
используется напрямую. FaultExceptions обеспечивают FaultCode, а другое исключение грозит как неизвестное с кодом ошибки -1.
Исключения FaultException отправляются с кодом состояния HTTP 400, а другие исключения - с кодом HTTP 500 - внутренняя ошибка сервера. В этом нет необходимости, так как код неисправности можно использовать для определения наличия и неизвестной ошибки. Однако это было удобно в моем приложении.
Обработчик ошибок
internal class CustomErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
//Tell the system that we handle all errors here.
return true;
}
public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
{
if (error is FaultException<int>)
{
FaultException<int> fe = (FaultException<int>)error;
//Detail for the returned value
int faultCode = fe.Detail;
string cause = fe.Message;
//The json serializable object
JsonError msErrObject = new JsonError { Message = cause, FaultCode = faultCode };
//The fault to be returned
fault = Message.CreateMessage(version, "", msErrObject, new DataContractJsonSerializer(msErrObject.GetType()));
// tell WCF to use JSON encoding rather than default XML
WebBodyFormatMessageProperty wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
// Add the formatter to the fault
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
//Modify response
HttpResponseMessageProperty rmp = new HttpResponseMessageProperty();
// return custom error code, 400.
rmp.StatusCode = System.Net.HttpStatusCode.BadRequest;
rmp.StatusDescription = "Bad request";
//Mark the jsonerror and json content
rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
rmp.Headers["jsonerror"] = "true";
//Add to fault
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
}
else
{
//Arbitraty error
JsonError msErrObject = new JsonError { Message = error.Message, FaultCode = -1};
// create a fault message containing our FaultContract object
fault = Message.CreateMessage(version, "", msErrObject, new DataContractJsonSerializer(msErrObject.GetType()));
// tell WCF to use JSON encoding rather than default XML
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
//Modify response
var rmp = new HttpResponseMessageProperty();
rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
rmp.Headers["jsonerror"] = "true";
//Internal server error with exception mesasage as status.
rmp.StatusCode = System.Net.HttpStatusCode.InternalServerError;
rmp.StatusDescription = error.Message;
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
}
}
#endregion
}
Webbehaviour используется для установки вышеуказанного обработчика ошибок
internal class AddErrorHandlerBehavior : WebHttpBehavior
{
protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
base.AddServerErrorHandlers(endpoint, endpointDispatcher);
//Remove all other error handlers
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
//Add our own
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new CustomErrorHandler());
}
}
Контракт данных об ошибках json
Определяет формат ошибки json. Добавьте свойства здесь, чтобы изменить формат ошибки.
[DataContractFormat]
public class JsonError
{
[DataMember]
public string Message { get; set; }
[DataMember]
public int FaultCode { get; set; }
}
Использование обработчика ошибок
Самопринятый
ServiceHost wsHost = new ServiceHost(new Webservice1(), new Uri("http://localhost/json"));
ServiceEndpoint wsEndpoint = wsHost.AddServiceEndpoint(typeof(IWebservice1), new WebHttpBinding(), string.Empty);
wsEndpoint.Behaviors.Add(new AddErrorHandlerBehavior());
App.config
<extensions>
<behaviorExtensions>
<add name="errorHandler"
type="WcfServiceLibrary1.ErrorHandlerElement, WcfServiceLibrary1" />
</behaviorExtensions>
</extensions>
Единственный способ вернуть детализацию исключений -
<serviceDebug includeExceptionDetailInFaults="true"/>
Я попробовал предложенный метод с расширением HttpWebBehavior, но когда он используется с enableWebScript, как показано ниже:
<behavior name="JsonBehavior">
<myWebHttp/>
<enableWebScript/>
</behavior>
Вызов WCF вернет код состояния 202 без какой-либо информации. Если конфигурация превращается в
<behavior name="JsonBehavior">
<enableWebScript/>
<myWebHttp/>
</behavior>
Вы получите обратно отформатированное сообщение, но потеряете все функции форматирования json, а также разбор параметров запроса из enableWebScript, что полностью противоречит цели использования enableWebScript.
Я также пытался использовать FaultContract, но, похоже, он работает только для Service References, а не для вызовов AJAX из JQuery.
Было бы неплохо иметь возможность переопределить WebScriptEnablingBehavior или сам сервис для обеспечения настраиваемой обработки ошибок.
У меня была та же проблема, что и у Бернда Бумюллера и user446861, и в конце я просто вернулся к использованию в качестве поведения для моей службы WCF. И вот, вам больше не нужны никакие вещи из IErrorHandler. Вы можете просто выбросить типы WebFaultException/WebFaultException, и все будет хорошо на стороне клиента.
webHttp - это то же самое, что и enableWebScript (webscript происходит от webHttp из того, что я понимаю), за исключением ASP.NET Ajax (ScriptManager ServiceReference и все такое). Поскольку я использую jQuery, мне не нужны автоматически сгенерированные прокси службы JS и другой багаж Ajax.net. Это прекрасно сработало для моих нужд, поэтому я подумал, что я оставлю комментарий на тот случай, если кто-то еще будет искать какую-то информацию.
Похоже, что предпочтительный способ различения состояний ошибки - через код состояния http в ответе. Это можно установить вручную в методе сервиса, но я не уверен, что это лучший способ подойти к этому или нет:
[ServiceContract(Namespace = "MyNamespace")]
AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyService
{
[OperationContract]
public void DoStuff(string param1, string etc)
{
try
{
//Do some stuff that maybe causes an exception
}
catch(ExceptionType1 ex)
{
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest;
}
catch(ExceptionType2 ex)
{
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Conflict;
}
... etc.
}
}
Я бы предложил обернуть исключение и разрешить всем исключениям проходить через сервис. Те, которые вы ожидаете, вы отфильтруете (как в приведенном выше примере) со значимыми сообщениями. Общий случай просто скажет:
генерировать новое ApplicationException("Неизвестная ошибка");
Таким образом, вы не будете сообщать клиенту информацию о внутренней работе службы, но сможете показывать значимые сообщения в тех случаях, когда вам необходимо передать клиенту информацию об ошибках, например, исключения безопасности и т. Д.