2016-07-06 54 views
8

Ho implementato IErrorHandler per gestire le eccezioni di autorizzazione generate all'interno del costruttore del mio servizio WCF riposante. Quando viene rilevata un'eccezione generale, il mio tipo personalizzato viene restituito come previsto, ma l'intestazione ContentType non è corretta.IErrorHandler che restituisce il corpo del messaggio errato quando il codice di stato HTTP è 401 Non autorizzato

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

{"ErrorMessage":"Error!"} 

Tuttavia, quando il gestore di errore tenta di restituire un codice di stato HTTP 401 non autorizzato il corpo del messaggio viene sovrascritto per il tipo predefinito, ma l'intestazione ContentType è come dovrebbe essere.

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

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

Ovviamente c'è qualcosa di sbagliato qui, ma io non sono sicuro di quello.

Come implementare IErrorHandler in modo che restituisca il mio tipo personalizzato in json con le intestazioni corrette?

BaseDataResponseContract Oggetto:

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

} // end 

Questo è l'oggetto voglio tornare. Tutti gli altri oggetti nella mia applicazione ereditano da questo oggetto. Quando viene lanciata un'eccezione, ci interessa solo il codice di stato http e il messaggio di errore.

IErrorHandler attuazione (la registrazione non è mostrato per brevità):

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

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> 

Infine, sto vedendo un comportamento simile quando si utilizza WebFaultException. Il mio pensiero è che questo è il risultato di alcuni shenanigans .Net profondamente sepolti. Sto scegliendo di implementare IErrorHandler in modo da poter rilevare eventuali altre eccezioni che potrebbero non essere gestite.

Riferimento:

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

Altri esempi:

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?

risposta

0

Dopo aver lottato con questo per quasi un giorno intero ho scoperto che questo era causato da un'impostazione di IIS.

Nell'ambito del mio progetto API in IIS, nel menu Autenticazione avevo impostato "Autenticazione moduli" su "Abilitato". Ho disattivato questa 'funzione' e il codice sopra ha iniziato a funzionare come previsto. Ho scoperto che ciò era dovuto a un altro sviluppatore del mio team che inserisce codice nel file web.config che modificava le impostazioni in IIS. In particolare:

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

Inoltre, sono stato in grado di ottenere l'intestazione ContentType di apparire correttamente utilizzando il ContentType proprietà sull'oggetto WebOperationContext OutgoingResponse.

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

Non sono abbastanza sicuro di come la vostra applicazione sia implementata. In base alla tua descrizione, ti suggerisco di utilizzare Visual Studio per eseguire il debug di ErrorHandler per verificare se l'eccezione arriva con la tua callback.

Se sì, costruisci manualmente l'errore o la risposta del sapone nel modo desiderato.

In caso contrario, significa che si verifica un'eccezione prima di arrivare all'operazione di servizio, potrebbe non riuscire già nello stack di canali, in questo caso, un approccio semplice è aggiungere HttpModule aggiuntivo per personalizzare o mappare la risposta. Oppure puoi provare l'encoder personalizzato in Channel stack.

+0

Sì ... ma come? Ho ottenuto lo stesso risultato con un WebOperationContext.current.OutgoingResponse ma non ho un metodo Write in esso, quindi come creare una risposta personalizzata? – DestyNova

0

Base su ciò che si scrive, si genera un'eccezione nel costruttore dell'implementazione del servizio. Poiché WCF utilizza la reflection per creare l'implementazione del servizio, a meno che il servizio non sia Singleton, si otterrà una TargetInvocationException.

Esempio (uso 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 
{ 

} 

In sostanza, evitare di gettare eccezioni basate sulla logica di business in un costruttore. Fallo solo per gestire errori di programmazione.

Problemi correlati