Возврат сведений об ошибке из службы 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("Неизвестная ошибка");

Таким образом, вы не будете сообщать клиенту информацию о внутренней работе службы, но сможете показывать значимые сообщения в тех случаях, когда вам необходимо передать клиенту информацию об ошибках, например, исключения безопасности и т. Д.

Другие вопросы по тегам