【问题标题】:How to make custom WCF error handler return JSON response with non-OK http code?如何使自定义 WCF 错误处理程序返回带有非 OK http 代码的 JSON 响应?
【发布时间】:2010-11-12 01:14:57
【问题描述】:

我正在使用 WCF 和 WebHttpBinding 实现一个 RESTful Web 服务。目前我正在研究错误处理逻辑,实现自定义错误处理程序(IErrorHandler);目的是让它捕获操作抛出的任何未捕获的异常,然后返回一个 JSON 错误对象(包括错误代码和错误消息 - 例如 { "errorCode": 123, "errorMessage": "bla" })浏览器用户以及 HTTP 代码,例如 BadRequest、InteralServerError 或其他任何内容(实际上是“OK”以外的任何内容)。这是我在错误处理程序的 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);

--> 这会返回 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);

--> 这会返回正确的状态码,但是内容类型现在是 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;

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

有什么想法吗?我正在考虑使用最后一种方法,只是将 JSON 粘贴在 HTTP StatusMessage 标头字段而不是正文中,但这似乎不太好?

【问题讨论】:

  • 你解决了这个问题吗?我也有同样的问题。

标签: .net wcf rest error-handling web-services


【解决方案1】:

实际上,这对我有用。

这是我的 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; }
    }

结合上面最后一个sn-p:

        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; 

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

【讨论】:

  • 请注意,根据 MSDN 文档,不能保证 ProvidFault 方法与服务操作在同一线程上执行,因此访问 WebOperationContext.Current 之类的东西可能会碰巧。跨度>
【解决方案2】:

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

是的,你有。 您可以创建自定义错误处理程序并做您想做的事。

见附件代码。

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

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);
    }
}

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

/// <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
    }
}

这是一个自定义绑定,因此您可以在 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
        };
    }
}

这就是 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>

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

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

 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】:

在最新版本的 WCF(截至 11/2011)中,使用 WebFaultException 有更好的方法来执行此操作。您可以在服务捕获块中按如下方式使用它:

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】:

    我遇到了完全相同的问题。这对我很有用:

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

    【讨论】:

    【解决方案5】:

    仔细检查您的 errorObject 是否可以由 DataContractJsonSerializer 序列化。我遇到了一个问题,我的合约实现没有为其中一个属性提供设置器,并且默默地无法序列化——导致类似的症状:“服务器没有发送响应”。

    这是我用来获取有关序列化错误的更多详细信息的代码(使用断言进行良好的单元测试,而没有用于断点目的的 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】:

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

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

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

    【讨论】:

      【解决方案7】:

      ErrorMessage 类是什么样的?

      不要将 StatusMessage 字段用于机器可读数据——请参阅https://www.rfc-editor.org/rfc/rfc2616#section-6.1.1

      此外,“http 正文现在有文本 'Failed to load source for: http://localhost:7000/bla..' 而不是实际的 JSON 数据..”可能没问题——文字如果我没记错的话,字符串是 JSON 数据。

      【讨论】:

        【解决方案8】:

        这是我想出的解决方案:

        Catching exceptions from WCF Web Services

        基本上,您让您的网络服务设置一个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;
                }
        }
        

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

        这是我在 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);
        
            });
        

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

        酷,嘿?

        完全通用,无需在您的服务返回的[DataContract] 数据中添加SuccessErrorMessage 字段。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-02-25
          • 2021-11-07
          • 1970-01-01
          • 1970-01-01
          • 2018-09-10
          相关资源
          最近更新 更多