Возврат сведений об ошибке из службы 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" - это всего лишь общий метод обработки ошибок, который отображает подробности об ошибке.

Ответ 1

РЕДАКТИРОВАТЬ: Обновлено сообщение с надлежащим пользовательским обработчиком ошибок 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 error

Определяет формат ошибки 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> 

Ответ 2

Единственный способ, с помощью которого я могу получить подробную информацию об исключении, - это

<serviceDebug includeExceptionDetailInFaults="true"/>

Я попробовал предложенный метод с расширением HttpWebBehavior, но когда он используется с enableWebScript следующим образом:

<behavior name="JsonBehavior">
    <myWebHttp/>
    <enableWebScript/>
</behavior>

Вызов WCF возвращает код состояния 202 без какой-либо информации. Если конфигурация превращается в

<behavior name="JsonBehavior">
        <enableWebScript/>
        <myWebHttp/>
    </behavior>

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

Я также попытался использовать FaultContract, но, похоже, работает только для ссылок на службы, а не из вызовов AJAX из JQuery.

Было бы неплохо переопределить WebScriptEnablingBehavior или сам сервис для обеспечения пользовательской обработки ошибок.

Ответ 3

У меня была такая же проблема, как у Бернда Бумюллера и пользователя446861, и в конце этого я просто вернулась к использованию в качестве поведения для моей службы WCF. Послушайте, вам больше не понадобится материал IErrorHandler. Вы можете просто бросить типы WebFaultException/WebFaultException, и на стороне клиента все будет золотым.

webHttp в основном такой же, как enableWebScript (webscript происходит из webHttp из того, что я понимаю) за вычетом ASP.NET файла Ajax (ScriptManager ServiceReference и всего этого). Поскольку я использую jQuery, мне не нужны автогенерированные прокси-серверы JS и другой багаж Ajax.net. Это прекрасно работало для моих нужд, поэтому просто подумал, что отправлю комментарий на всякий случай, если кто-то еще ищет какую-то информацию.

Ответ 4

Похоже, что предпочтительный способ различать состояния ошибок - через код состояния 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.
    }
}

Ответ 5

Я бы предложил обернуть исключение и разрешить все исключения проходить через завернутую службу. Те, которые вы ожидаете, будут отфильтрованы (как в приведенном выше примере) с содержательными сообщениями. В общем случае просто скажем:

throw new ApplicationException ( "Неизвестная ошибка" );

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