2009-07-19 2 views
31

Ich implementiere einen RESTful Web-Service mit WCF und WebHttpBinding. Zurzeit arbeite ich an der Fehlerbehandlungslogik und implementiere einen benutzerdefinierten Fehlerhandler (IErrorHandler); Ziel ist es irgendwelche abgefangene Ausnahmen von Operationen geworfen fangen und dann ein JSON-Fehler-Objekt zurück (einschließlich einem Fehlercode und Fehlermeldung sagen - zB { „errorcode“: 123, „errormessage“: „bla“}) auf die Rückseite Browser-Benutzer zusammen mit einem HTTP-Code wie BadRequest, InteralServerError oder was auch immer (etwas anderes als 'OK' wirklich). Hier ist der Code, den ich in der ProvideFault Methode meines Fehlerhandler bin mit:Wie kann benutzerdefinierte WCF-Fehlerhandler JSON-Antwort mit nicht-OK-HTTP-Code zurückgeben?

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

-> Das gibt mit Content-Type: application/json jedoch der Statuscode 'OK' anstelle von 'InternalServerError' ist .

-> Dies gibt mit dem richtigen Statuscode zurück, aber der Inhaltstyp ist jetzt 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; 

-> Das gibt mit dem richtigen Statuscode und dem richtigen Content-Type! Das Problem ist, dass der HTTP-Körper jetzt den Text hat "Fehler beim Laden der Quelle für: http://localhost:7000/bla .." anstelle der tatsächlichen JSON-Daten.

Irgendwelche Ideen? Ich überlege, den letzten Ansatz zu verwenden und nur den JSON in das HTTP StatusMessage-Header-Feld anstatt in den Körper zu stecken, aber das scheint nicht ganz so nett zu sein?

+4

Haben Sie das behoben? Ich habe das gleiche Problem. – tucaz

Antwort

0

Was bedeutet die Errormessage-Klasse aussehen?

Verwenden Sie das Statusmessage Feld nicht für maschinenlesbare Daten - siehe http://tools.ietf.org/html/rfc2616#section-6.1.1.

Es kann auch in Ordnung sein, dass "der HTTP-Body jetzt den Text 'Fehler beim Laden der Quelle für: http://localhost:7000/bla ..' anstelle der tatsächlichen JSON-Daten .." - eine literale Zeichenfolge ist JSON-Daten, wenn ich mich erinnere korrekt.

25

Eigentlich ist dies für mich funktioniert.

Hier ist meine Klasse 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; } 
    } 

mit dem letzten Schnipsel Kombinierter oben:

 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; 

Das gibt mir die richtigen Fehler als json. Vielen Dank. :)

6

In der neuesten Version von WCF (Stand 11/2011) gibt es einen besseren Weg, um diese mit WebFaultException zu tun. Sie können es verwenden, wie in Ihrem Service catch-Blöcke folgt:

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

Hier ist eine komplette Lösung auf ein paar Informationen basierend von oben:

Ja Sie haben. Sie können benutzerdefinierte Fehlerbehandlung erstellen und tun, was Ihnen gefällt.

Siehe die beigefügten Code.

, dass die benutzerdefinierte Fehlerhandler ist:

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

, das eine erweiterte Service-Verhalten ist die Fehlerbehandlung zu injizieren:

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

Das ist eine individuelle Bindung so können Sie um es in der web.config

zu konfigurieren
/// <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 
     }; 
    } 
} 

Das ist die 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> 

Hinweis: Das Verhalten Erweiterung sollte genau in einer Linie sein, wie (es ist ein Fehler in WCF ist).

Das ist mein Client-Seite (Teil unserer benutzerdefinierten 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); 
       } 
      }); 
    } 
+1

Vielen Dank dafür! Es funktioniert und wird doppelten Fehlerbehandlungscode in meiner Anwendung reduzieren. Gibt es eine Idee, wie man diese Implementierung testen kann? –

+0

Sie benötigen Komponententest. Erstellen Sie einfach einen Dienst, der eine Ausnahme auslöst, und einen Client, der sie aufruft. Stellen Sie dann sicher, dass die Antwort wie erwartet ist. – nadavy

1

doppelt überprüfen, ob Ihre errorObject von DataContractJsonSerializer serialisiert werden kann. Ich stieß auf ein Problem, bei dem meine Vertragsimplementierung keinen Setter für eine der Eigenschaften zur Verfügung stellte und im Stillen nicht serialisiert wurde - was zu ähnlichen Symptomen führte: "Server hat keine Antwort gesendet".

Hier ist der Code, den ich verwenden, um mehr Details über die Serialisierung Fehler angezeigt (macht einen guter Unit-Test mit einer Behauptung und ohne try/catch für Stützpunkt Zwecke):

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

Danke für diesen Tipp! Das war der Grund, warum mein Fall nicht funktionierte –

0

Hier ist die Lösung, die ich kam mit:

Catching exceptions from WCF Web Services

Grundsätzlich erhalten Sie Ihren Web-Dienst eine OutgoingWebResponseContext Variable zu setzen, und das Rück null als Ergebnis

(ja, wirklich!)

Dann erhalten Sie Ihren Aufrufer auf Fehler zu überprüfen, dann überprüfen, ob ein "statusText" -Wert zurückgegeben wurde.

Hier ist, wie ich tat es in Eckig:

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

    }); 

Und hier ist, was mein Eckige Code in IE angezeigt:

Error in IE

Cool, he?

Vollständig generisch, und keine Notwendigkeit, Success oder ErrorMessage Felder zu den [DataContract] Daten, die Ihre Dienste zurückgeben.

0

Wenn Sie Web-Apps zum Aufrufen von WFC verwenden, geben Sie Ihren JSON immer als Stream zurück. Für Fehler, keine Notwendigkeit für eine Reihe von ausgefallenen/hässlichen Code.Ändern Sie einfach den HTTP-Statuscode mit:

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

Dann statt die Ausnahme zu werfen, formatieren Sie diese Ausnahme oder eine benutzerdefinierte Fehler Objekt in JSON und schicken Sie es als System.IO.Stream.