2009-07-19 16 views
31

Tôi đang triển khai dịch vụ web RESTful bằng WCF và WebHttpBinding. Hiện tại tôi đang làm việc trên logic xử lý lỗi, triển khai trình xử lý lỗi tùy chỉnh (IErrorHandler); mục đích là để nó bắt bất kỳ trường hợp ngoại lệ chưa được khai thác nào do hoạt động và sau đó trả về một đối tượng lỗi JSON (bao gồm cả mã lỗi và thông báo lỗi - ví dụ: {"errorCode": 123, "errorMessage": "bla"}) trở lại người dùng trình duyệt cùng với một mã HTTP như BadRequest, InteralServerError hoặc bất cứ điều gì (bất cứ điều gì khác hơn là 'OK' thực sự). Đây là mã tôi đang sử dụng bên trong phương pháp ProvideFault của xử lý lỗi của tôi:Làm thế nào để làm cho trình xử lý lỗi WCF tùy chỉnh trả về phản hồi JSON với mã http không phải là OK?

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

-> Đây trả về với Content-Type: application/json, tuy nhiên các mã trạng thái là 'OK' thay vì '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); 

-> Điều này trả về với mã trạng thái chính xác, tuy nhiên loại nội dung hiện là 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; 

-> Điều này trả về đúng mã trạng thái và loại nội dung chính xác! Vấn đề là cơ thể http hiện có văn bản 'Không thể tải nguồn cho: http://localhost:7000/bla ..' thay vì dữ liệu JSON thực tế.

Bất kỳ ý tưởng nào? Tôi đang xem xét việc sử dụng cách tiếp cận cuối cùng và chỉ gắn bó JSON vào trường tiêu đề HTTP StatusMessage thay vì trong phần thân, nhưng điều này có vẻ không đẹp?

+4

Bạn có quản lý để sửa lỗi này không? Im có cùng một vấn đề. – tucaz

Trả lời

0

Lớp ErrorMessage trông như thế nào?

Không sử dụng trường StatusMessage cho dữ liệu có thể đọc được trên máy - xem http://tools.ietf.org/html/rfc2616#section-6.1.1.

Ngoài ra, có thể được rằng "cơ thể http hiện có văn bản 'Không thể tải nguồn cho: http://localhost:7000/bla ..' thay vì dữ liệu JSON thực tế .." - chuỗi chữ là dữ liệu JSON nếu tôi nhớ đúng.

25

Thực ra, điều này phù hợp với tôi.

Đây là lớp ErrorMessage của tôi:

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

Kết hợp với đoạn cuối cùng trên:

 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; 

này mang lại cho tôi lỗi đúng như json. Cảm ơn. :)

6

Trong phiên bản mới nhất của WCF (Kể từ 11/2011), có cách tốt hơn để thực hiện việc này bằng cách sử dụng WebFaultException. Bạn có thể sử dụng nó như sau trong khối catch dịch vụ của bạn:

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

Dưới đây là một giải pháp hoàn chỉnh dựa trên một số thông tin từ trên cao:

Có bạn có. Bạn có thể tạo trình xử lý lỗi tùy chỉnh và thực hiện những gì bạn cảm thấy.

Xem mã được đính kèm.

Đó là xử lý lỗi tùy chỉnh:

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

Đó là một hành vi dịch vụ mở rộng để tiêm xử lý lỗi:

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

Đó là một phong tục ràng buộc, do đó bạn sẽ có thể để định cấu hình nó trong 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 
     }; 
    } 
} 

Đó là 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> 

Lưu ý: Phần mở rộng hành vi phải ở trong một dòng CHÍNH XÁC như là (có một lỗi trong WCF).

Đó là phía khách hàng của tôi (một phần của proxy tùy chỉnh của chúng tôi)

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

Cảm ơn bạn vì điều này! Nó hoạt động và sẽ giảm mã xử lý lỗi trùng lặp trong ứng dụng của tôi. Bất kỳ ý tưởng làm thế nào để đi về đơn vị kiểm tra thực hiện này? –

+0

Bạn sẽ cần thử nghiệm thành phần. Chỉ cần tạo một dịch vụ mà ném một ngoại lệ và một khách hàng gọi nó. Sau đó, khẳng định rằng phản hồi như mong đợi. – nadavy

1

Kiểm tra rằng errorObject của bạn có thể được đăng bởi DataContractJsonSerializer. Tôi gặp phải vấn đề trong đó triển khai hợp đồng của tôi không cung cấp một setter cho một trong các thuộc tính và âm thầm không serialize - dẫn đến các triệu chứng tương tự: 'server không gửi phản hồi'.

Dưới đây là đoạn code tôi sử dụng để biết thêm chi tiết về lỗi serialization (làm cho một thử nghiệm đơn vị tốt với một khẳng định và không có try/catch cho các mục đích breakpoint):

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(); 
+0

Cảm ơn mẹo này! Đây là lý do trường hợp của tôi không hoạt động –

0

Dưới đây là giải pháp tôi đã đưa ra với:

Catching exceptions from WCF Web Services

về cơ bản, bạn sẽ có được dịch vụ web của bạn để thiết lập một biến OutgoingWebResponseContext, và trở null như kết quả

(vâng, thực sự!)
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; 
     } 
} 

Sau đó, bạn yêu cầu người gọi tìm lỗi, sau đó kiểm tra xem giá trị "statusText" đã được trả về chưa.

Đây là cách tôi đã làm nó trong góc:

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

    }); 

Và đây là những gì mã kiễu góc của tôi hiển thị trong IE:

Error in IE

mát, hey?

Hoàn toàn chung chung và không cần phải thêm Success hoặc ErrorMessage trường vào dữ liệu [DataContract] mà dịch vụ của bạn đang trả lại.

0

Đối với những người sử dụng ứng dụng web để gọi WFC, hãy luôn trả lại JSON của bạn dưới dạng Luồng. Đối với các lỗi, không cần cho một loạt các mã ưa thích/xấu xí.Chỉ cần thay đổi mã trạng thái http:

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

Sau đó thay vì ném ngoại lệ, định dạng ngoại lệ hoặc đối tượng lỗi tùy chỉnh vào JSON và trả về dưới dạng System.IO.Stream.

Các vấn đề liên quan