2016-07-06 4 views
8

Ich habe IErrorHandler implementiert, um Autorisierungsausnahmen zu verarbeiten, die im Konstruktor meines WCF-Diensts ausgelöst wurden. Wenn eine allgemeine Ausnahme abgefangen wird, wird mein benutzerdefinierter Typ wie erwartet zurückgegeben, aber der ContentType-Header ist falsch.IErrorHandler gibt falschen Nachrichtentext zurück, wenn der HTTP-Statuscode 401 lautet Nicht autorisiert

HTTP/1.1 500 Internal Server Error 
Content-Type: application/xml; 
... 

{"ErrorMessage":"Error!"} 

jedoch, wenn die Fehler-Handler versucht, einen 401 Unauthorized HTTP-Statuscode der Nachrichtentext auf den Standardtyp außer Kraft gesetzt wird, zurückzukehren, aber der Contenttype-Header ist wie es sein sollte.

HTTP/1.1 401 Unauthorized 
Content-Type: application/json; 
... 

{"Message":"Authentication failed.","StackTrace":null,"ExceptionType":"System.InvalidOperationException"} 

Offensichtlich stimmt hier etwas nicht, aber ich bin nicht sicher was.

Wie implementiere ich IErrorHandler so, dass es meinen benutzerdefinierten Typ in JSON mit den richtigen Headern zurückgibt?

BaseDataResponseContract Objekt:

[Serializable] 
[DataContract(Name = "BaseDataResponseContract")] 
public class BaseDataResponseContract 
{ 
    [DataMember] 
    public string ErrorMessage { get; set; } 

} // end 

Dies ist das Objekt, das ich zurückkehren möchten. Alle anderen Objekte in meiner Anwendung erben von diesem Objekt. Wenn eine Ausnahme ausgelöst wird, interessiert uns nur der HTTP-Statuscode und die Fehlermeldung.

IErrorHandler Implementation (Protokollierung der Kürze halber nicht dargestellt):

namespace WebServices.BehaviorsAndInspectors 
{ 
    public class ErrorHandler : IErrorHandler 
    { 
     public bool HandleError(Exception error) 
     { 
      return true; 

     } // end 

     public void ProvideFault(Exception ex, MessageVersion version, ref Message fault) 
     { 
      // Create a new instance of the object I would like to return with a default message 
      var baseDataResponseContract = new BaseDataResponseContract { ErrorMessage = "Error!" }; 

      // Get the outgoing response portion of the current context 
      var response = WebOperationContext.Current.OutgoingResponse; 

      // Set the http status code 
      response.StatusCode = HttpStatusCode.InternalServerError; 

      // If the exception is a specific type change the default settings 
      if (ex.GetType() == typeof(UserNotFoundException)) 
      { 
       baseDataResponseContract.ErrorMessage = "Invalid Username!"; 
       response.StatusCode = HttpStatusCode.Unauthorized; 
      }  

      // Create the fault message that is returned (note the ref parameter) 
      fault = Message.CreateMessage(version, "", baseDataResponseContract, new DataContractJsonSerializer(typeof(BaseDataResponseContract))); 

      // Tell WCF to use JSON encoding rather than default XML 
      var webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json); 
      fault.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty); 

      // Add ContentType header that specifies we are using json 
      var httpResponseMessageProperty = new HttpResponseMessageProperty(); 
      httpResponseMessageProperty.Headers[HttpResponseHeader.ContentType] = "application/json"; 
      fault.Properties.Add(HttpResponseMessageProperty.Name, httpResponseMessageProperty); 

     } // end 

    } // end class 

} // end namespace 

IServiceBehavior Umsetzung:

namespace WebServices.BehaviorsAndInspectors 
{ 
    public class ErrorHandlerExtensionBehavior : BehaviorExtensionElement, IServiceBehavior 
    { 
     public override Type BehaviorType 
     { 
      get { return GetType(); } 
     } 

     protected override object CreateBehavior() 
     { 
      return this; 
     } 

     private IErrorHandler GetInstance() 
     { 
      return new ErrorHandler(); 
     } 

     void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } // end 

     void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) 
     { 
      var errorHandlerInstance = GetInstance(); 

      foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers) 
      { 
       dispatcher.ErrorHandlers.Add(errorHandlerInstance); 
      } 
     } 

     void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } // end 

    } // end class 

} // end namespace 

Web.Config:

<system.serviceModel> 

    <services>  
     <service name="WebServices.MyService"> 
     <endpoint binding="webHttpBinding" contract="WebServices.IMyService" /> 
     </service> 
    </services> 

    <extensions>  
     <behaviorExtensions>   
     <!-- This extension if for the WCF Error Handling--> 
     <add name="ErrorHandlerBehavior" type="WebServices.BehaviorsAndInspectors.ErrorHandlerExtensionBehavior, WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />  
     </behaviorExtensions>  
    </extensions> 

    <behaviors>   
     <serviceBehaviors>   
     <behavior> 
      <serviceMetadata httpGetEnabled="true"/> 
      <serviceDebug includeExceptionDetailInFaults="true"/> 
      <ErrorHandlerBehavior /> 
     </behavior>  
     </serviceBehaviors>  
    </behaviors> 

    .... 
</system.serviceModel> 

Schließlich sehe ich ein ähnliches Verhalten bei Verwendung von WebFaultException. Mein Gedanke ist, dass dies das Ergebnis einiger tief vergrabener .Net-Spielereien ist. Ich entscheide mich dafür, IErrorHandler zu implementieren, damit ich alle anderen Ausnahmen abfangen kann, die nicht behandelt werden können.

Referenz:

https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.100).aspx

http://www.brainthud.com/cards/5218/25441/which-four-behavior-interfaces-exist-for-interacting-with-a-service-or-client-description-what-methods-do-they-implement-and

Andere Beispiele:

IErrorHandler doesn't seem to be handling my errors in WCF .. any ideas?

How to make custom WCF error handler return JSON response with non-OK http code?

How do you set the Content-Type header for an HttpClient request?

Antwort

0

Nachdem ich fast einen ganzen Tag damit zu kämpfen hatte, entdeckte ich, dass dies durch eine IIS-Einstellung verursacht wurde.

Unter meinem API-Projekt in IIS hatte ich im Menü Authentifizierung die 'Formularauthentifizierung' auf 'Aktiviert' gesetzt. Ich habe diese Funktion deaktiviert und der obige Code hat wie erwartet funktioniert. Ich habe festgestellt, dass dies auf einen anderen Entwickler in meinem Team zurückzuführen ist, der Code in die Datei web.config geschrieben hat, die die Einstellungen in IIS geändert hat. Im Einzelnen:

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
    ... 
    <system.web> 
     <authentication mode="Forms" /> 
    </system.web> 
    ... 
</configuration> 

Ferner konnte ich den Contenttype-Header erhält korrekt angezeigt werden, indem die Contenttype-Eigenschaft auf dem Objekt WebOperationContext OutgoingResponse verwenden.

// Get the outgoing response portion of the current context 
var response = WebOperationContext.Current.OutgoingResponse; 

// Add ContentType header that specifies we are using JSON 
response.ContentType = new MediaTypeHeaderValue("application/json").ToString(); 
0

Ich bin mir nicht ganz sicher, wie Ihre Anwendung implementiert ist. Basierend auf Ihrer Beschreibung, schlage ich vor, Visual Studio zu verwenden, um Ihren ErrorHandler zu debuggen, um zu sehen, ob die Exception zu Ihrem Callback kommt.

Wenn ja, konstruieren Sie Ihren Seifenfehler oder Ihre Antwort manuell so, wie Sie möchten.

Wenn nicht, bedeutet dies, dass die Ausnahme auftritt, bevor Sie Ihren Service-Betrieb, kann es bereits in Channel-Stack, in diesem Fall eine einfache Vorgehensweise hinzufügen zusätzliche HttpModule benutzerdefinierte oder zuordnen die Antwort. Oder Sie können den Encoder im Channel-Stack anpassen.

+0

Ja ... aber wie? Ich habe den gleichen progrem mit einem WebOperationContext.current.OutgoingResponse, aber ich habe keine Schreibmethode darin, so wie man eine benutzerdefinierte Antwort macht? – DestyNova

0

Basierend auf dem, was Sie schreiben, werfen Sie eine Exception in den Konstruktor der Service-Implementierung. Da WCF Reflection zum Erstellen Ihrer Dienstimplementierung verwendet, erhalten Sie eine TargetInvocationException, sofern es sich bei Ihrem Dienst nicht um Singleton handelt.

Beispiel (Verwendung LINQPad):

void Main() 
{ 
    try 
    { 
     Activator.CreateInstance(typeof(Foo)); 
    } 
    catch(Exception e) 
    { 
     e.Message.Dump(); 
     e.GetType().Name.Dump(); 
    } 
} 

public class Foo 
{ 
    public Foo() 
    { 
     throw new AuthorizationFailedException(); 
    } 
} 

public class AuthorizationFailedException : Exception 
{ 

} 

Grundsätzlich vermeiden Ausnahmen basierend auf Business-Logik in einem Konstruktor werfen. Tun Sie das nur für die Behandlung von Programmierfehlern.

Verwandte Themen