如何使用非OK http代码使自定义WCF错误处理程序返回JSON响应?

时间:2022-11-08 00:19:06

I'm implementing a RESTful web service using WCF and the WebHttpBinding. Currently I'm working on the error handling logic, implementing a custom error handler (IErrorHandler); the aim is to have it catch any uncaught exceptions thrown by operations and then return a JSON error object (including say an error code and error message - e.g. { "errorCode": 123, "errorMessage": "bla" }) back to the browser user along with an an HTTP code such as BadRequest, InteralServerError or whatever (anything other than 'OK' really). Here is the code I am using inside the ProvideFault method of my error handler:

我正在使用WCF和WebHttpBinding实现RESTful Web服务。目前我正在研究错误处理逻辑,实现自定义错误处理程序(IErrorHandler);目的是让它捕获操作抛出的任何未捕获的异常,然后返回一个JSON错误对象(包括说错误代码和错误消息 - 例如{“errorCode”:123,“errorMessage”:“bla”})返回到浏览器用户以及一个HTTP代码,例如BadRequest,InteralServerError或其他任何东西(除了'确定'以外的任何东西)。这是我在错误处理程序的ProvideFault方法中使用的代码:

fault = Message.CreateMessage(version, "", errorObject, new DataContractJsonSerializer(typeof(ErrorMessage)));
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
var rmp = new HttpResponseMessageProperty();
rmp.StatusCode = System.Net.HttpStatusCode.InternalServerError;
rmp.Headers.Add(HttpRequestHeader.ContentType, "application/json");
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);

--> This returns with Content-Type: application/json, however the status code is 'OK' instead of 'InternalServerError'.

- >返回Content-Type:application / json,但状态代码为'OK'而不是'InternalServerError'。

fault = Message.CreateMessage(version, "", errorObject, new DataContractJsonSerializer(typeof(ErrorMessage)));
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
var rmp = new HttpResponseMessageProperty();
rmp.StatusCode = System.Net.HttpStatusCode.InternalServerError;
//rmp.Headers.Add(HttpRequestHeader.ContentType, "application/json");
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);

--> This returns with the correct status code, however the content-type is now XML.

- >返回正确的状态代码,但内容类型现在是XML。

fault = Message.CreateMessage(version, "", errorObject, new DataContractJsonSerializer(typeof(ErrorMessage)));
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);

var response = WebOperationContext.Current.OutgoingResponse;
response.ContentType = "application/json";
response.StatusCode = HttpStatusCode.InternalServerError;

--> This returns with the correct status code and the correct content-type! The problem is that the http body now has the text 'Failed to load source for: http://localhost:7000/bla..' instead of the actual JSON data..

- >返回正确的状态代码和正确的内容类型!问题是http主体现在有文本'无法加载源:http:// localhost:7000 / bla ..'而不是实际的JSON数据..

Any ideas? I'm considering using the last approach and just sticking the JSON in the HTTP StatusMessage header field instead of in the body, but this doesn't seem quite as nice?

有任何想法吗?我正在考虑使用最后一种方法,只是将JSON放在HTTP StatusMessage头字段而不是在正文中,但这看起来不是很好吗?

8 个解决方案

#1


25  

Actually, this works for me.

实际上,这对我有用。

Here's my ErrorMessage class:

这是我的ErrorMessage类:

    [DataContract]
    public class ErrorMessage
    {
        public ErrorMessage(Exception error)
        {
            Message = error.Message;
            StackTrace = error.StackTrace;
            Exception = error.GetType().Name;
        }

        [DataMember(Name="stacktrace")]
        public string StackTrace { get; set; }
        [DataMember(Name = "message")]
        public string Message { get; set; }
        [DataMember(Name = "exception-name")]
        public string Exception { get; set; }
    }

Combined with the last snippet above:

结合上面的最后一个片段:

        fault = Message.CreateMessage(version, "", new ErrorMessage(error), new DataContractJsonSerializer(typeof(ErrorMessage)));
        var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
        fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);

        var response = WebOperationContext.Current.OutgoingResponse;
        response.ContentType = "application/json";
        response.StatusCode = HttpStatusCode.InternalServerError; 

This gives me proper errors as json. Thanks. :)

这给了我正确的错误,如json。谢谢。 :)

#2


13  

Here's a complete solution based on some info from above:

以下是基于上述信息的完整解决方案:

Yes you have. You can create custom error handler and do what you feel like.

是的,你有。您可以创建自定义错误处理程序并执行您的操作。

See the attached code.

请参阅附带的代码。

That's the custom error handler:

这是自定义错误处理程序:

public class JsonErrorHandler : IErrorHandler
{

    public bool HandleError(Exception error)
    {
        // Yes, we handled this exception...
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        // Create message
        var jsonError = new JsonErrorDetails { Message = error.Message, ExceptionType = error.GetType().FullName };
        fault = Message.CreateMessage(version, "", jsonError,
                                      new DataContractJsonSerializer(typeof(JsonErrorDetails)));

        // 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
                      {
                          StatusCode = HttpStatusCode.BadRequest,
                          StatusDescription = "Bad Request",
                      };
        rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
        fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
    }
}

That's an extended service behavior to inject the error handler:

这是注入错误处理程序的扩展服务行为:

/// <summary>
/// This class is a custom implementation of the WebHttpBehavior. 
/// The main of this class is to handle exception and to serialize those as requests that will be understood by the web application.
/// </summary>
public class ExtendedWebHttpBehavior : WebHttpBehavior
{
    protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        // clear default erro handlers.
        endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();

        // add our own error handler.
        endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new JsonErrorHandler());
        //BehaviorExtensionElement
    }
}

That's a custom binding so you'll be able to configure it in the web.config

这是一个自定义绑定,因此您可以在web.config中配置它

/// <summary>
/// Enables the ExtendedWebHttpBehavior for an endpoint through configuration.
/// Note: Since the ExtendedWebHttpBehavior is derived of the WebHttpBehavior we wanted to have the exact same configuration.  
/// However during the coding we've relized that the WebHttpElement is sealed so we've grabbed its code using reflector and
/// modified it to our needs.
/// </summary>
public sealed class ExtendedWebHttpElement : BehaviorExtensionElement
{
    private ConfigurationPropertyCollection properties;
    /// <summary>Gets or sets a value that indicates whether help is enabled.</summary>
    /// <returns>true if help is enabled; otherwise, false. </returns>
    [ConfigurationProperty("helpEnabled")]
    public bool HelpEnabled
    {
        get
        {
            return (bool)base["helpEnabled"];
        }
        set
        {
            base["helpEnabled"] = value;
        }
    }
    /// <summary>Gets and sets the default message body style.</summary>
    /// <returns>One of the values defined in the <see cref="T:System.ServiceModel.Web.WebMessageBodyStyle" /> enumeration.</returns>
    [ConfigurationProperty("defaultBodyStyle")]
    public WebMessageBodyStyle DefaultBodyStyle
    {
        get
        {
            return (WebMessageBodyStyle)base["defaultBodyStyle"];
        }
        set
        {
            base["defaultBodyStyle"] = value;
        }
    }
    /// <summary>Gets and sets the default outgoing response format.</summary>
    /// <returns>One of the values defined in the <see cref="T:System.ServiceModel.Web.WebMessageFormat" /> enumeration.</returns>
    [ConfigurationProperty("defaultOutgoingResponseFormat")]
    public WebMessageFormat DefaultOutgoingResponseFormat
    {
        get
        {
            return (WebMessageFormat)base["defaultOutgoingResponseFormat"];
        }
        set
        {
            base["defaultOutgoingResponseFormat"] = value;
        }
    }
    /// <summary>Gets or sets a value that indicates whether the message format can be automatically selected.</summary>
    /// <returns>true if the message format can be automatically selected; otherwise, false. </returns>
    [ConfigurationProperty("automaticFormatSelectionEnabled")]
    public bool AutomaticFormatSelectionEnabled
    {
        get
        {
            return (bool)base["automaticFormatSelectionEnabled"];
        }
        set
        {
            base["automaticFormatSelectionEnabled"] = value;
        }
    }
    /// <summary>Gets or sets the flag that specifies whether a FaultException is generated when an internal server error (HTTP status code: 500) occurs.</summary>
    /// <returns>Returns true if the flag is enabled; otherwise returns false.</returns>
    [ConfigurationProperty("faultExceptionEnabled")]
    public bool FaultExceptionEnabled
    {
        get
        {
            return (bool)base["faultExceptionEnabled"];
        }
        set
        {
            base["faultExceptionEnabled"] = value;
        }
    }
    protected override ConfigurationPropertyCollection Properties
    {
        get
        {
            if (this.properties == null)
            {
                this.properties = new ConfigurationPropertyCollection
                {
                    new ConfigurationProperty("helpEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None), 
                    new ConfigurationProperty("defaultBodyStyle", typeof(WebMessageBodyStyle), WebMessageBodyStyle.Bare, null, null, ConfigurationPropertyOptions.None), 
                    new ConfigurationProperty("defaultOutgoingResponseFormat", typeof(WebMessageFormat), WebMessageFormat.Xml, null, null, ConfigurationPropertyOptions.None), 
                    new ConfigurationProperty("automaticFormatSelectionEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None), 
                    new ConfigurationProperty("faultExceptionEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None)
                };
            }
            return this.properties;
        }
    }
    /// <summary>Gets the type of the behavior enabled by this configuration element.</summary>
    /// <returns>The <see cref="T:System.Type" /> for the behavior enabled with the configuration element: <see cref="T:System.ServiceModel.Description.WebHttpBehavior" />.</returns>
    public override Type BehaviorType
    {
        get
        {
            return typeof(ExtendedWebHttpBehavior);
        }
    }
    protected override object CreateBehavior()
    {
        return new ExtendedWebHttpBehavior
        {
            HelpEnabled = this.HelpEnabled,
            DefaultBodyStyle = this.DefaultBodyStyle,
            DefaultOutgoingResponseFormat = this.DefaultOutgoingResponseFormat,
            AutomaticFormatSelectionEnabled = this.AutomaticFormatSelectionEnabled,
            FaultExceptionEnabled = this.FaultExceptionEnabled
        };
    }
}

That's the web.config

那是web.config

  <system.serviceModel>
<diagnostics>
  <messageLogging logMalformedMessages="true" logMessagesAtTransportLevel="true" />
</diagnostics>
<bindings>
  <webHttpBinding>
    <binding name="regularService" />
  </webHttpBinding>
</bindings>
<behaviors>
  <endpointBehaviors>
    <behavior name="AjaxBehavior">
      <extendedWebHttp />
    </behavior>
  </endpointBehaviors>
  <serviceBehaviors>
    <behavior>
      <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
      <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
      <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
      <serviceDebug includeExceptionDetailInFaults="true"/>
    </behavior>
  </serviceBehaviors>
</behaviors>
<extensions>
  <behaviorExtensions>
    <add name="extendedWebHttp" type="MyNamespace.ExtendedWebHttpElement, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  </behaviorExtensions>
</extensions>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
  <service name="MyWebService">
    <endpoint address="" behaviorConfiguration="AjaxBehavior"
      binding="webHttpBinding" bindingConfiguration="regularService"
      contract="IMyWebService" />
  </service>
</services>

Note: The behavior extension should be in one line EXACTLY as is (there's a bug in WCF).

注意:行为扩展应该完全按原样排在一行(WCF中有一个错误)。

That's my client side (part of our custom proxy)

这是我的客户端(我们的自定义代理的一部分)

 public void Invoke<T>(string action, object prms, JsAction<T> successCallback, JsAction<WebServiceException> errorCallback = null, JsBoolean webGet = null)
    {
        Execute(new WebServiceRequest { Action = action, Parameters = prms, UseGetMethod = webGet },
            t =>
            {
                successCallback(t.As<T>());
            },
            (req, message, err)=>
            {
                if (req.status == 400) //Bad request - that's what we've specified in the WCF error handler.
                {
                    var details = JSON.parse(req.responseText).As<JsonErrorDetails>();
                    var ex = new WebServiceException()
                    {
                        Message = details.Message,
                        StackTrace = details.StackTrace,
                        Type = details.ExceptionType
                    };

                    errorCallback(ex);
                }
            });
    }

#3


6  

In the latest version of WCF (As of 11/2011) there's a better way of doing this using WebFaultException. You can use it as follows in your service catch blocks:

在最新版本的WCF(截至2011年11月)中,使用WebFaultException有更好的方法。您可以在服务catch块中按如下方式使用它:

throw new WebFaultException<ServiceErrorDetail>(new ServiceErrorDetail(ex), HttpStatusCode.SeeOther);


[DataContract]
    public class ServiceErrorDetail
    {
        public ServiceErrorDetail(Exception ex)
        {
            Error = ex.Message;
            Detail = ex.Source;
        }
        [DataMember]
        public String Error { get; set; }
        [DataMember]
        public String Detail { get; set; }
    }

#4


3  

I had the exact same problem. This was useful for me:

我有同样的问题。这对我很有用:

http://zamd.net/2008/07/08/error-handling-with-webhttpbinding-for-ajaxjson/

http://zamd.net/2008/07/08/error-handling-with-webhttpbinding-for-ajaxjson/

#5


1  

Double-check that your errorObject can be serialized by DataContractJsonSerializer. I ran into a problem where my contract implementation didn't provide a setter for one of the properties and was silently failing to serialize--resulting in similar symptoms: 'server did not send a response'.

仔细检查DataContractJsonSerializer是否可以序列化您的errorObject。我遇到了一个问题,我的合同实现没有提供其中一个属性的setter,并且无声地无法序列化 - 导致类似的症状:'服务器没有发送响应'。

Here's the code I used to get more details about the serialization error (makes a good unit test with an assertion and without the try/catch for breakpoint purposes):

这是我用来获取有关序列化错误的更多细节的代码(使用断言进行良好的单元测试,并且没有用于断点的try / catch):

Stream s = new MemoryStream();
try
{
    new DataContractJsonSerializer(typeof(ErrorObjectDataContractClass)).WriteObject(s, errorObject);
} catch(Exception e)
{
    e.ToString();
}
s.Seek(0, SeekOrigin.Begin);
var json = new StreamReader(s, Encoding.UTF8).ReadToEnd();

#6


0  

What does the ErrorMessage class look like?

ErrorMessage类是什么样的?

Don't use the StatusMessage field for machine-readable data -- see http://tools.ietf.org/html/rfc2616#section-6.1.1 .

不要将StatusMessage字段用于机器可读数据 - 请参阅http://tools.ietf.org/html/rfc2616#section-6.1.1。

Also, it may be okay that "the http body now has the text 'Failed to load source for: http://localhost:7000/bla..' instead of the actual JSON data.." -- a literal string is JSON data if I remember correctly.

此外,它可能没关系“http主体现在有文本'无法加载源:http:// localhost:7000 / bla ..'而不是实际的JSON数据..” - 文字字符串是JSON如果我没记错的数据。

#7


0  

Here's the solution I came up with:

这是我提出的解决方案:

Catching exceptions from WCF Web Services

从WCF Web服务捕获异常

Basically, you get your web service to set a OutgoingWebResponseContext variable, and return null as the result (yes, really !)

基本上,您获得Web服务来设置OutgoingWebResponseContext变量,并返回null作为结果(是的,真的!)

    public List<string> GetAllCustomerNames()
    {
        //  Get a list of unique Customer names.
        //
        try 
        {
            //  As an example, let's throw an exception, for our Angular to display..
            throw new Exception("Oh heck, something went wrong !");

            NorthwindDataContext dc = new NorthwindDataContext();
            var results = (from cust in dc.Customers select cust.CompanyName).Distinct().OrderBy(s => s).ToList();

            return results;
        }  
        catch (Exception ex)
        {
            OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
            response.StatusCode = System.Net.HttpStatusCode.Forbidden;
            response.StatusDescription = ex.Message;
            return null;
        }
}

Then, you get your caller to look out for errors, then check if a "statusText" value was returned.

然后,让调用者查找错误,然后检查是否返回了“statusText”值。

Here's how I did it in Angular:

这是我在Angular中的表现:

$http.get('http://localhost:15021/Service1.svc/getAllCustomerNames')
    .then(function (data) {
        //  We successfully loaded the list of Customer names.
        $scope.ListOfCustomerNames = data.GetAllCustomerNamesResult;

    }, function (errorResponse) {

        //  The WCF Web Service returned an error

        var HTTPErrorNumber = errorResponse.status;
        var HTTPErrorStatusText = errorResponse.statusText;

        alert("An error occurred whilst fetching Customer Names\r\nHTTP status code: " + HTTPErrorNumber + "\r\nError: " + HTTPErrorStatusText);

    });

And here's what my Angular code displayed in IE:

这是我的Angular代码在IE中显示的内容:

如何使用非OK http代码使自定义WCF错误处理程序返回JSON响应?

Cool, hey ?

很酷,嘿?

Completely generic, and no need to add Success or ErrorMessage fields to the [DataContract] data which your services are returning.

完全通用,无需将Success或ErrorMessage字段添加到服务返回的[DataContract]数据中。

#8


0  

For those using web apps to call WFC, always return your JSON as a Stream. For errors, no need for a bunch of fancy/ugly code. Just change the http status code with:

对于那些使用Web应用程序调用WFC的用户,请始终将JSON作为Stream返回。对于错误,不需要一堆花哨/丑陋的代码。只需更改http状态代码:

System.ServiceModel.Web.WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.InternalServerError

Then instead of throwing the exception, format that exception or a custom error object into JSON and return it as a System.IO.Stream.

然后,不是抛出异常,而是将该异常或自定义错误对象格式化为JSON并将其作为System.IO.Stream返回。

#1


25  

Actually, this works for me.

实际上,这对我有用。

Here's my ErrorMessage class:

这是我的ErrorMessage类:

    [DataContract]
    public class ErrorMessage
    {
        public ErrorMessage(Exception error)
        {
            Message = error.Message;
            StackTrace = error.StackTrace;
            Exception = error.GetType().Name;
        }

        [DataMember(Name="stacktrace")]
        public string StackTrace { get; set; }
        [DataMember(Name = "message")]
        public string Message { get; set; }
        [DataMember(Name = "exception-name")]
        public string Exception { get; set; }
    }

Combined with the last snippet above:

结合上面的最后一个片段:

        fault = Message.CreateMessage(version, "", new ErrorMessage(error), new DataContractJsonSerializer(typeof(ErrorMessage)));
        var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
        fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);

        var response = WebOperationContext.Current.OutgoingResponse;
        response.ContentType = "application/json";
        response.StatusCode = HttpStatusCode.InternalServerError; 

This gives me proper errors as json. Thanks. :)

这给了我正确的错误,如json。谢谢。 :)

#2


13  

Here's a complete solution based on some info from above:

以下是基于上述信息的完整解决方案:

Yes you have. You can create custom error handler and do what you feel like.

是的,你有。您可以创建自定义错误处理程序并执行您的操作。

See the attached code.

请参阅附带的代码。

That's the custom error handler:

这是自定义错误处理程序:

public class JsonErrorHandler : IErrorHandler
{

    public bool HandleError(Exception error)
    {
        // Yes, we handled this exception...
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        // Create message
        var jsonError = new JsonErrorDetails { Message = error.Message, ExceptionType = error.GetType().FullName };
        fault = Message.CreateMessage(version, "", jsonError,
                                      new DataContractJsonSerializer(typeof(JsonErrorDetails)));

        // 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
                      {
                          StatusCode = HttpStatusCode.BadRequest,
                          StatusDescription = "Bad Request",
                      };
        rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
        fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
    }
}

That's an extended service behavior to inject the error handler:

这是注入错误处理程序的扩展服务行为:

/// <summary>
/// This class is a custom implementation of the WebHttpBehavior. 
/// The main of this class is to handle exception and to serialize those as requests that will be understood by the web application.
/// </summary>
public class ExtendedWebHttpBehavior : WebHttpBehavior
{
    protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        // clear default erro handlers.
        endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();

        // add our own error handler.
        endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new JsonErrorHandler());
        //BehaviorExtensionElement
    }
}

That's a custom binding so you'll be able to configure it in the web.config

这是一个自定义绑定,因此您可以在web.config中配置它

/// <summary>
/// Enables the ExtendedWebHttpBehavior for an endpoint through configuration.
/// Note: Since the ExtendedWebHttpBehavior is derived of the WebHttpBehavior we wanted to have the exact same configuration.  
/// However during the coding we've relized that the WebHttpElement is sealed so we've grabbed its code using reflector and
/// modified it to our needs.
/// </summary>
public sealed class ExtendedWebHttpElement : BehaviorExtensionElement
{
    private ConfigurationPropertyCollection properties;
    /// <summary>Gets or sets a value that indicates whether help is enabled.</summary>
    /// <returns>true if help is enabled; otherwise, false. </returns>
    [ConfigurationProperty("helpEnabled")]
    public bool HelpEnabled
    {
        get
        {
            return (bool)base["helpEnabled"];
        }
        set
        {
            base["helpEnabled"] = value;
        }
    }
    /// <summary>Gets and sets the default message body style.</summary>
    /// <returns>One of the values defined in the <see cref="T:System.ServiceModel.Web.WebMessageBodyStyle" /> enumeration.</returns>
    [ConfigurationProperty("defaultBodyStyle")]
    public WebMessageBodyStyle DefaultBodyStyle
    {
        get
        {
            return (WebMessageBodyStyle)base["defaultBodyStyle"];
        }
        set
        {
            base["defaultBodyStyle"] = value;
        }
    }
    /// <summary>Gets and sets the default outgoing response format.</summary>
    /// <returns>One of the values defined in the <see cref="T:System.ServiceModel.Web.WebMessageFormat" /> enumeration.</returns>
    [ConfigurationProperty("defaultOutgoingResponseFormat")]
    public WebMessageFormat DefaultOutgoingResponseFormat
    {
        get
        {
            return (WebMessageFormat)base["defaultOutgoingResponseFormat"];
        }
        set
        {
            base["defaultOutgoingResponseFormat"] = value;
        }
    }
    /// <summary>Gets or sets a value that indicates whether the message format can be automatically selected.</summary>
    /// <returns>true if the message format can be automatically selected; otherwise, false. </returns>
    [ConfigurationProperty("automaticFormatSelectionEnabled")]
    public bool AutomaticFormatSelectionEnabled
    {
        get
        {
            return (bool)base["automaticFormatSelectionEnabled"];
        }
        set
        {
            base["automaticFormatSelectionEnabled"] = value;
        }
    }
    /// <summary>Gets or sets the flag that specifies whether a FaultException is generated when an internal server error (HTTP status code: 500) occurs.</summary>
    /// <returns>Returns true if the flag is enabled; otherwise returns false.</returns>
    [ConfigurationProperty("faultExceptionEnabled")]
    public bool FaultExceptionEnabled
    {
        get
        {
            return (bool)base["faultExceptionEnabled"];
        }
        set
        {
            base["faultExceptionEnabled"] = value;
        }
    }
    protected override ConfigurationPropertyCollection Properties
    {
        get
        {
            if (this.properties == null)
            {
                this.properties = new ConfigurationPropertyCollection
                {
                    new ConfigurationProperty("helpEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None), 
                    new ConfigurationProperty("defaultBodyStyle", typeof(WebMessageBodyStyle), WebMessageBodyStyle.Bare, null, null, ConfigurationPropertyOptions.None), 
                    new ConfigurationProperty("defaultOutgoingResponseFormat", typeof(WebMessageFormat), WebMessageFormat.Xml, null, null, ConfigurationPropertyOptions.None), 
                    new ConfigurationProperty("automaticFormatSelectionEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None), 
                    new ConfigurationProperty("faultExceptionEnabled", typeof(bool), false, null, null, ConfigurationPropertyOptions.None)
                };
            }
            return this.properties;
        }
    }
    /// <summary>Gets the type of the behavior enabled by this configuration element.</summary>
    /// <returns>The <see cref="T:System.Type" /> for the behavior enabled with the configuration element: <see cref="T:System.ServiceModel.Description.WebHttpBehavior" />.</returns>
    public override Type BehaviorType
    {
        get
        {
            return typeof(ExtendedWebHttpBehavior);
        }
    }
    protected override object CreateBehavior()
    {
        return new ExtendedWebHttpBehavior
        {
            HelpEnabled = this.HelpEnabled,
            DefaultBodyStyle = this.DefaultBodyStyle,
            DefaultOutgoingResponseFormat = this.DefaultOutgoingResponseFormat,
            AutomaticFormatSelectionEnabled = this.AutomaticFormatSelectionEnabled,
            FaultExceptionEnabled = this.FaultExceptionEnabled
        };
    }
}

That's the web.config

那是web.config

  <system.serviceModel>
<diagnostics>
  <messageLogging logMalformedMessages="true" logMessagesAtTransportLevel="true" />
</diagnostics>
<bindings>
  <webHttpBinding>
    <binding name="regularService" />
  </webHttpBinding>
</bindings>
<behaviors>
  <endpointBehaviors>
    <behavior name="AjaxBehavior">
      <extendedWebHttp />
    </behavior>
  </endpointBehaviors>
  <serviceBehaviors>
    <behavior>
      <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
      <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
      <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
      <serviceDebug includeExceptionDetailInFaults="true"/>
    </behavior>
  </serviceBehaviors>
</behaviors>
<extensions>
  <behaviorExtensions>
    <add name="extendedWebHttp" type="MyNamespace.ExtendedWebHttpElement, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  </behaviorExtensions>
</extensions>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
  <service name="MyWebService">
    <endpoint address="" behaviorConfiguration="AjaxBehavior"
      binding="webHttpBinding" bindingConfiguration="regularService"
      contract="IMyWebService" />
  </service>
</services>

Note: The behavior extension should be in one line EXACTLY as is (there's a bug in WCF).

注意:行为扩展应该完全按原样排在一行(WCF中有一个错误)。

That's my client side (part of our custom proxy)

这是我的客户端(我们的自定义代理的一部分)

 public void Invoke<T>(string action, object prms, JsAction<T> successCallback, JsAction<WebServiceException> errorCallback = null, JsBoolean webGet = null)
    {
        Execute(new WebServiceRequest { Action = action, Parameters = prms, UseGetMethod = webGet },
            t =>
            {
                successCallback(t.As<T>());
            },
            (req, message, err)=>
            {
                if (req.status == 400) //Bad request - that's what we've specified in the WCF error handler.
                {
                    var details = JSON.parse(req.responseText).As<JsonErrorDetails>();
                    var ex = new WebServiceException()
                    {
                        Message = details.Message,
                        StackTrace = details.StackTrace,
                        Type = details.ExceptionType
                    };

                    errorCallback(ex);
                }
            });
    }

#3


6  

In the latest version of WCF (As of 11/2011) there's a better way of doing this using WebFaultException. You can use it as follows in your service catch blocks:

在最新版本的WCF(截至2011年11月)中,使用WebFaultException有更好的方法。您可以在服务catch块中按如下方式使用它:

throw new WebFaultException<ServiceErrorDetail>(new ServiceErrorDetail(ex), HttpStatusCode.SeeOther);


[DataContract]
    public class ServiceErrorDetail
    {
        public ServiceErrorDetail(Exception ex)
        {
            Error = ex.Message;
            Detail = ex.Source;
        }
        [DataMember]
        public String Error { get; set; }
        [DataMember]
        public String Detail { get; set; }
    }

#4


3  

I had the exact same problem. This was useful for me:

我有同样的问题。这对我很有用:

http://zamd.net/2008/07/08/error-handling-with-webhttpbinding-for-ajaxjson/

http://zamd.net/2008/07/08/error-handling-with-webhttpbinding-for-ajaxjson/

#5


1  

Double-check that your errorObject can be serialized by DataContractJsonSerializer. I ran into a problem where my contract implementation didn't provide a setter for one of the properties and was silently failing to serialize--resulting in similar symptoms: 'server did not send a response'.

仔细检查DataContractJsonSerializer是否可以序列化您的errorObject。我遇到了一个问题,我的合同实现没有提供其中一个属性的setter,并且无声地无法序列化 - 导致类似的症状:'服务器没有发送响应'。

Here's the code I used to get more details about the serialization error (makes a good unit test with an assertion and without the try/catch for breakpoint purposes):

这是我用来获取有关序列化错误的更多细节的代码(使用断言进行良好的单元测试,并且没有用于断点的try / catch):

Stream s = new MemoryStream();
try
{
    new DataContractJsonSerializer(typeof(ErrorObjectDataContractClass)).WriteObject(s, errorObject);
} catch(Exception e)
{
    e.ToString();
}
s.Seek(0, SeekOrigin.Begin);
var json = new StreamReader(s, Encoding.UTF8).ReadToEnd();

#6


0  

What does the ErrorMessage class look like?

ErrorMessage类是什么样的?

Don't use the StatusMessage field for machine-readable data -- see http://tools.ietf.org/html/rfc2616#section-6.1.1 .

不要将StatusMessage字段用于机器可读数据 - 请参阅http://tools.ietf.org/html/rfc2616#section-6.1.1。

Also, it may be okay that "the http body now has the text 'Failed to load source for: http://localhost:7000/bla..' instead of the actual JSON data.." -- a literal string is JSON data if I remember correctly.

此外,它可能没关系“http主体现在有文本'无法加载源:http:// localhost:7000 / bla ..'而不是实际的JSON数据..” - 文字字符串是JSON如果我没记错的数据。

#7


0  

Here's the solution I came up with:

这是我提出的解决方案:

Catching exceptions from WCF Web Services

从WCF Web服务捕获异常

Basically, you get your web service to set a OutgoingWebResponseContext variable, and return null as the result (yes, really !)

基本上,您获得Web服务来设置OutgoingWebResponseContext变量,并返回null作为结果(是的,真的!)

    public List<string> GetAllCustomerNames()
    {
        //  Get a list of unique Customer names.
        //
        try 
        {
            //  As an example, let's throw an exception, for our Angular to display..
            throw new Exception("Oh heck, something went wrong !");

            NorthwindDataContext dc = new NorthwindDataContext();
            var results = (from cust in dc.Customers select cust.CompanyName).Distinct().OrderBy(s => s).ToList();

            return results;
        }  
        catch (Exception ex)
        {
            OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
            response.StatusCode = System.Net.HttpStatusCode.Forbidden;
            response.StatusDescription = ex.Message;
            return null;
        }
}

Then, you get your caller to look out for errors, then check if a "statusText" value was returned.

然后,让调用者查找错误,然后检查是否返回了“statusText”值。

Here's how I did it in Angular:

这是我在Angular中的表现:

$http.get('http://localhost:15021/Service1.svc/getAllCustomerNames')
    .then(function (data) {
        //  We successfully loaded the list of Customer names.
        $scope.ListOfCustomerNames = data.GetAllCustomerNamesResult;

    }, function (errorResponse) {

        //  The WCF Web Service returned an error

        var HTTPErrorNumber = errorResponse.status;
        var HTTPErrorStatusText = errorResponse.statusText;

        alert("An error occurred whilst fetching Customer Names\r\nHTTP status code: " + HTTPErrorNumber + "\r\nError: " + HTTPErrorStatusText);

    });

And here's what my Angular code displayed in IE:

这是我的Angular代码在IE中显示的内容:

如何使用非OK http代码使自定义WCF错误处理程序返回JSON响应?

Cool, hey ?

很酷,嘿?

Completely generic, and no need to add Success or ErrorMessage fields to the [DataContract] data which your services are returning.

完全通用,无需将Success或ErrorMessage字段添加到服务返回的[DataContract]数据中。

#8


0  

For those using web apps to call WFC, always return your JSON as a Stream. For errors, no need for a bunch of fancy/ugly code. Just change the http status code with:

对于那些使用Web应用程序调用WFC的用户,请始终将JSON作为Stream返回。对于错误,不需要一堆花哨/丑陋的代码。只需更改http状态代码:

System.ServiceModel.Web.WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.InternalServerError

Then instead of throwing the exception, format that exception or a custom error object into JSON and return it as a System.IO.Stream.

然后,不是抛出异常,而是将该异常或自定义错误对象格式化为JSON并将其作为System.IO.Stream返回。