2016-07-06 11 views
8

Zaimplementowałem IErrorHandler do obsługi wyjątków autoryzacji generowanych w konstruktorze mojej resturacyjnej usługi WCF. Po wykryciu ogólnego wyjątku mój typ niestandardowy jest zwracany zgodnie z oczekiwaniami, ale nagłówek ContentType jest niepoprawny.IErrorHandler zwraca nieprawidłową treść wiadomości, gdy kod stanu HTTP jest 401 Nieautoryzowany

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

{"ErrorMessage":"Error!"} 

Jednak gdy obsługi błędów usiłuje powrócić 401 nieautoryzowany kod stanu HTTP treść wiadomości jest zastępowany do domyślnego typu ale nagłówku contenttype jest tak jak powinno być.

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

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

Oczywiście coś tu jest nie tak, ale nie jestem pewien co.

Jak zaimplementować IErrorHandler w taki sposób, że zwraca mój niestandardowy typ w jsonie z poprawnymi nagłówkami?

BaseDataResponseContract obiektu:

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

} // end 

Jest to obiekt chcę wrócić. Wszystkie inne obiekty w mojej aplikacji dziedziczą z tego obiektu. Podczas generowania wyjątku bardzo zależy nam na kodzie statusu HTTP i komunikacie o błędzie.

IErrorHandler Realizacja (rejestracja nie jest pokazany na zwięzłość):

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

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> 

Wreszcie widzę podobne zachowanie podczas korzystania z wyjątku WebFaultException. Uważam, że jest to wynikiem niektórych głęboko zakopanych szantażystów .Net. Wybieram implementację IErrorHandler, aby móc wychwycić wszelkie inne wyjątki, które mogą nie być obsługiwane.

referencyjne:

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

innych przykładów:

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?

Odpowiedz

0

Po zmaganiu się z tym przez prawie cały dzień odkryłem, że było to spowodowane ustawieniem IIS.

Pod moim projektem API w IIS, w menu Uwierzytelnianie miałem "Uwierzytelnianie formularzy" ustawione na "Włączone". Wyłączyłem tę "funkcję", a powyższy kod zaczął działać zgodnie z oczekiwaniami. Zauważyłem, że było to spowodowane innym programistą w moim zespole umieszczającym kod w pliku web.config, który zmienił ustawienia w IIS. Konkretnie:

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

Ponadto, udało mi się dostać nagłówek ContentType do wyświetlane prawidłowo za pomocą ContentType obiekt na obiekcie 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

Nie jestem do końca pewien, w jaki sposób wdrażana jest Twoja aplikacja. Opierając się na twoim opisie, sugeruję użycie Visual Studio do debugowania ErrorHandler'a, aby zobaczyć, czy wyjątek dotarł do twojego callbacka.

Jeśli tak, ręcznie skonstruuj błąd lub odpowiedź mydła w sposób, w jaki chcesz.

Jeśli nie, oznacza to, że wyjątek ma miejsce przed rozpoczęciem operacji serwisowej, może on już zawieść w stosie kanałów, w tym przypadku prostym sposobem jest dodanie dodatkowego modułu Http do odpowiednika lub odwzorowania odpowiedzi. Możesz też spróbować dostosować koder w stosie kanałów.

+0

Tak ... ale jak? Mam taki sam progrem z WebOperationContext.current.OutgoingResponse, ale nie mam w nim metody zapisu, więc jak stworzyć niestandardową odpowiedź? – DestyNova

0

W oparciu o to, co piszesz, rzucasz wyjątek w konstruktorze implementacji usługi. Ponieważ WCF używa refleksji do tworzenia implementacji usług, chyba że twoja usługa to Singleton, dostaniesz wyjątek TargetInvocationException.

Przykład (użycie 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 
{ 

} 

Zasadniczo unikać rzucania wyjątków opartych na logice biznesowej w konstruktorze. Rób to tylko w przypadku błędów programowania.