2013-07-31 15 views
7

In pratica sto implementando l'interfaccia IErrorHandler per catturare tutti i tipi di eccezioni dal servizio WCF e inviarlo al client implementando il metodo ProvideFault.Gestione delle eccezioni WCF tramite IErrorHandler

Tuttavia, sto affrontando un problema critico. Tutte le eccezioni vengono inviate al client come FaultException ma ciò disabilita il client per gestire eccezioni specifiche che potrebbe aver definito nel servizio.

Considerare: SomeException che è stato definito e lanciato in una delle implementazioni OperationContract. Quando viene generata l'eccezione, la sua trasformato in un errore utilizzando il seguente codice:

var faultException = new FaultException(error.Message); 
MessageFault messageFault = faultException.CreateMessageFault(); 
fault = Message.CreateMessage(version, messageFault, faultException.Action); 

Ciò inviare l'errore come una stringa, ma il cliente deve prendere una deroga generale come:

try{...} 
catch(Exception e){...} 

e non:

try{...} 
catch(SomeException e){...} 

non solo eccezioni personalizzate come SomeException, ma le eccezioni di sistema come InvalidOperationException non possono essere catturati utilizzando il processo di cui sopra.

Qualche idea su come implementare questo comportamento?

risposta

9

In WCF auspicabile utilizzare le eccezioni speciali descritti come contratti, perché il cliente potrebbe non essere un'applicazione .NET che contiene informazioni su eccezioni standard .NET. Per fare ciò è possibile definire lo FaultContract nel proprio servizio e quindi utilizzare la classe FaultException.

Server Side

[ServiceContract] 
public interface ISampleService 
{ 
    [OperationContract] 
    [FaultContractAttribute(typeof(MyFaultMessage))] 
    string SampleMethod(string msg); 
} 

[DataContract] 
public class MyFaultMessage 
{ 
    public MyFaultMessage(string message) 
    { 
     Message = message; 
    } 

    [DataMember] 
    public string Message { get; set; } 
} 

class SampleService : ISampleService 
{ 
    public string SampleMethod(string msg) 
    { 
     throw new FaultException<MyFaultMessage>(new MyFaultMessage("An error occurred.")); 
    }   
} 

Inoltre, è possibile specificare nel file di configurazione che il server restituisce i dettagli di eccezione in esso è FaultExceptions, ma questo non è raccomandato in un'applicazione di produzione:

<serviceBehaviors> 
    <behavior> 
    <!-- 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> 

Successivamente, è possibile riscrivere il metodo per la gestione delle eccezioni:

var faultException = error as FaultException; 
if (faultException == null) 
{ 
    //If includeExceptionDetailInFaults = true, the fault exception with details will created by WCF. 
    return; 
} 
MessageFault messageFault = faultException.CreateMessageFault(); 
fault = Message.CreateMessage(version, messageFault, faultException.Action); 

CLIENTE:

try 
{ 
    _client.SampleMethod(); 
} 
catch (FaultException<MyFaultMessage> e) 
{ 
    //Handle    
} 
catch (FaultException<ExceptionDetail> exception) 
{ 
    //Getting original exception detail if includeExceptionDetailInFaults = true 
    ExceptionDetail exceptionDetail = exception.Detail; 
} 
0

FaultException ha una proprietà Codice che è possibile utilizzare per la gestione delle eccezioni.

try 
{ 
... 
} 
catch (FaultException ex) 
{ 
    switch(ex.Code) 
    { 
     case 0: 
      break; 
     ... 
    } 
} 
7

Questo articolo potrebbe aiutare:

http://www.olegsych.com/2008/07/simplifying-wcf-using-exceptions-as-faults/

Un approccio che ho usato con un certo successo quando non ho voglia di enumerare le eccezioni che potrebbero essere generato consiste nel creare una classe PassthroughExceptionHandlingBehavior che implementa un comportamento IErrorHandler per il lato server e IClientMessageInspector per il lato client. Il comportamento di IErrorHandler serializza l'eccezione nel messaggio di errore. IClientMessageInspector deserializza e genera l'eccezione.

È necessario associare questo comportamento sia al client WCF che al server WCF. È possibile collegare i comportamenti utilizzando il file di configurazione o applicando l'attributo [PassthroughExceptionHandlingBehavior] al proprio contratto.

Ecco la classe comportamento:

public class PassthroughExceptionHandlingBehavior : Attribute, IClientMessageInspector, IErrorHandler, 
    IEndpointBehavior, IServiceBehavior, IContractBehavior 
{ 
    #region IClientMessageInspector Members 

    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState) 
    { 
     if (reply.IsFault) 
     { 
      // Create a copy of the original reply to allow default processing of the message 
      MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue); 
      Message copy = buffer.CreateMessage(); // Create a copy to work with 
      reply = buffer.CreateMessage();   // Restore the original message 

      var exception = ReadExceptionFromFaultDetail(copy) as Exception; 
      if (exception != null) 
      { 
       throw exception; 
      } 
     } 
    } 

    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel) 
    { 
     return null; 
    } 

    private static object ReadExceptionFromFaultDetail(Message reply) 
    { 
     const string detailElementName = "detail"; 

     using (XmlDictionaryReader reader = reply.GetReaderAtBodyContents()) 
     { 
      // Find <soap:Detail> 
      while (reader.Read()) 
      { 
       if (reader.NodeType == XmlNodeType.Element && 
        detailElementName.Equals(reader.LocalName, StringComparison.InvariantCultureIgnoreCase)) 
       { 
        return ReadExceptionFromDetailNode(reader); 
       } 
      } 
      // Couldn't find it! 
      return null; 
     } 
    } 

    private static object ReadExceptionFromDetailNode(XmlDictionaryReader reader) 
    { 
     // Move to the contents of <soap:Detail> 
     if (!reader.Read()) 
     { 
      return null; 
     } 

     // Return the deserialized fault 
     try 
     { 
      NetDataContractSerializer serializer = new NetDataContractSerializer(); 
      return serializer.ReadObject(reader); 
     } 
     catch (SerializationException) 
     { 
      return null; 
     } 
    } 

    #endregion 

    #region IErrorHandler Members 

    public bool HandleError(Exception error) 
    { 
     return false; 
    } 

    public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault) 
    { 
     if (error is FaultException) 
     { 
      // Let WCF do normal processing 
     } 
     else 
     { 
      // Generate fault message manually including the exception as the fault detail 
      MessageFault messageFault = MessageFault.CreateFault(
       new FaultCode("Sender"), 
       new FaultReason(error.Message), 
       error, 
       new NetDataContractSerializer()); 
      fault = Message.CreateMessage(version, messageFault, null); 
     } 
    } 

    #endregion 

    #region IContractBehavior Members 

    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) 
    { 
    } 

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime) 
    { 
     ApplyClientBehavior(clientRuntime); 
    } 

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime) 
    { 
     ApplyDispatchBehavior(dispatchRuntime.ChannelDispatcher); 
    } 

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) 
    { 
    } 

    #endregion 

    #region IEndpointBehavior Members 

    public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) 
    { 
    } 

    public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) 
    { 
     ApplyClientBehavior(clientRuntime); 
    } 

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) 
    { 
     ApplyDispatchBehavior(endpointDispatcher.ChannelDispatcher); 
    } 

    public void Validate(ServiceEndpoint endpoint) 
    { 
    } 

    #endregion 

    #region IServiceBehavior Members 

    public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) 
    { 
    } 

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) 
    { 
     foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers) 
     { 
      ApplyDispatchBehavior(dispatcher); 
     } 
    } 

    public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) 
    { 
    } 

    #endregion 

    #region Behavior helpers 

    private static void ApplyClientBehavior(System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) 
    { 
     foreach (IClientMessageInspector messageInspector in clientRuntime.MessageInspectors) 
     { 
      if (messageInspector is PassthroughExceptionHandlingBehavior) 
      { 
       return; 
      } 
     } 

     clientRuntime.MessageInspectors.Add(new PassthroughExceptionHandlingBehavior()); 
    } 

    private static void ApplyDispatchBehavior(System.ServiceModel.Dispatcher.ChannelDispatcher dispatcher) 
    { 
     // Don't add an error handler if it already exists 
     foreach (IErrorHandler errorHandler in dispatcher.ErrorHandlers) 
     { 
      if (errorHandler is PassthroughExceptionHandlingBehavior) 
      { 
       return; 
      } 
     } 

     dispatcher.ErrorHandlers.Add(new PassthroughExceptionHandlingBehavior()); 
    } 

    #endregion 
} 

#region PassthroughExceptionHandlingElement class 

public class PassthroughExceptionExtension : BehaviorExtensionElement 
{ 
    public override Type BehaviorType 
    { 
     get { return typeof(PassthroughExceptionHandlingBehavior); } 
    } 

    protected override object CreateBehavior() 
    { 
     System.Diagnostics.Debugger.Launch(); 
     return new PassthroughExceptionHandlingBehavior(); 
    } 
} 

#endregion 
+0

quale metodo fuoco dopo HandleError? – KumarHarsh

+0

Posso collegare un gestore errori solo sul lato client? Voglio collegarlo all'implementazione 'ClientBase ', piuttosto al livello di servizio, è possibile? – Shimmy

+0

Sicuro. Io uso un metodo di supporto, qualcosa del genere: var factory = new ChannelFactory (binding, endpointAddress); factory.Endpoint.EndpointBehaviors.Add (new PassthroughExceptionHandlingBehavior()); return factory.CreateChannel(); –