2009-01-14 14 views
5

Estoy tratando de llegar a un patrón de diseño simple y fácil de usar para el manejo de errores en un servicio wcf .NET (específicamente un servicio wcf activado Silverlight). Si se lanza una excepción en el método de servicio, la aplicación Silverlight verá una CommunicationException que dice "El servidor remoto devolvió un error: NotFound --->" y posiblemente un seguimiento de pila dependiendo de su configuración, lo cual no es útil porque no lo hace Te digo el error real, y generalmente el error real no es "NotFound"..Net WFC/Web service manejo de excepciones patrón de diseño

Al leer servicios web y servicios wcf y excepciones, debe lanzar excepciones estándar de soap/wcf tales como FaultException o SoapException. Por lo tanto, para un servicio wcf necesita ajustar cada método en una captura de prueba, atrapar cada excepción, envolverlo en una FaultException y lanzarlo. Al menos eso es lo que entiendo, corrígeme si estoy equivocado.

Así que he creado mi patrón de diseño:

[ServiceContract(Namespace = "http://MyTest")] 
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] 
public class DataAccess 
{ 
    /// <summary> 
    /// Error class, handle converting an exception into a FaultException 
    /// </summary> 
    [DataContractAttribute] 
    public class Error 
    { 
     private string strMessage_m; 
     private string strStackTrace_m; 

     public Error(Exception ex) 
     { 
      this.strMessage_m = ex.Message; 
      this.strStackTrace_m = ex.StackTrace; 
     } 

     [DataMemberAttribute] 
     public string Message 
     { 
      get { return this.strMessage_m; } 
      set { this.strMessage_m = value; } 
     } 

     [DataMemberAttribute] 
     public string StackTrace 
     { 
      get { return this.strStackTrace_m; } 
      set { this.strStackTrace_m = value; } 
     } 

     //Convert an exception into a FaultException 
     public static void Throw(Exception ex) 
     { 
      if (ex is FaultException) 
      { 
       throw ex; 
      } 
      else 
      { 
       throw new FaultException<Error>(new Error(ex)); 
      } 
     } 
    } 

    [OperationContract] 
    [FaultContract(typeof(Error))] 
    public void TestException() 
    { 
     try 
     { 
      throw new Exception("test"); 
     } 
     catch (Exception ex) 
     { 
      Error.Throw(ex); 
     } 
    } 
} 

Así que para hacer una larga historia corta, todavía no recibo el error correcto en mi aplicación Silverlight. Inspecciono el objeto AsyncCompletedEventArgs.Error y todavía contiene un objeto CommunicationException con el error genérico. Ayúdenme a crear un patrón de diseño simple y agradable que me permita arrojar fácilmente la excepción correcta del servicio y atraparla fácilmente en la aplicación.

Respuesta

5

Ok, analicé la idea de IErrorHandler. No tenía idea de que pudieras hacerlo de esta manera, y es perfecto porque te permite evitar las capturas de prueba para cada método. ¿Puedes hacer esto en servicios web estándar también? He implementado el siguiente manera:

/// <summary> 
/// Services can intercept errors, perform processing, and affect how errors are reported using the 
/// IErrorHandler interface. The interface has two methods that can be implemented: ProvideFault and 
/// HandleError. The ProvideFault method allows you to add, modify, or suppress a fault message that 
/// is generated in response to an exception. The HandleError method allows error processing to take 
/// place in the event of an error and controls whether additional error handling can run. 
/// 
/// To use this class, specify it as the type in the ErrorBehavior attribute constructor. 
/// </summary> 
public class ServiceErrorHandler : IErrorHandler 
{ 
    /// <summary> 
    /// Default constructor 
    /// </summary> 
    public ServiceErrorHandler() 
    { 
    } 

    /// <summary> 
    /// Specifies a url of the service 
    /// </summary> 
    /// <param name="strUrl"></param> 
    public ServiceErrorHandler(string strUrl, bool bHandled) 
    { 
     this.strUrl_m = strUrl; 
     this.bHandled_m = bHandled; 
    } 

    /// <summary> 
    ///HandleError. Log an error, then allow the error to be handled as usual. 
    ///Return true if the error is considered as already handled 
    /// </summary> 
    /// <param name="error"></param> 
    /// <returns></returns> 
    public virtual bool HandleError(Exception exError) 
    { 
     System.Diagnostics.EventLog evt = new System.Diagnostics.EventLog("Application", ".", "My Application"); 
     evt.WriteEntry("Error at " + this.strUrl_m + ":\n" + exError.Message, System.Diagnostics.EventLogEntryType.Error); 

     return this.bHandled_m; 
    } 

    /// <summary> 
    ///Provide a fault. The Message fault parameter can be replaced, or set to 
    ///null to suppress reporting a fault. 
    /// </summary> 
    /// <param name="error"></param> 
    /// <param name="version"></param> 
    /// <param name="msg"></param> 
    public virtual void ProvideFault(Exception exError, 
     System.ServiceModel.Channels.MessageVersion version, 
     ref System.ServiceModel.Channels.Message msg) 
    { 
     //Any custom message here 
     /* 
     DataAccessFaultContract dafc = new DataAccessFaultContract(exError.Message); 

     System.ServiceModel.FaultException fe = new System.ServiceModel.FaultException<DataAccessFaultContract>(dafc); 
     System.ServiceModel.Channels.MessageFault fault = fe.CreateMessageFault(); 

     string ns = "http://www.example.com/services/FaultContracts/DataAccessFault"; 
     msg = System.ServiceModel.Channels.Message.CreateMessage(version, fault, ns); 
     */ 
    } 

    private string strUrl_m; 
    /// <summary> 
    /// Specifies a url of the service, displayed in the error log 
    /// </summary> 
    public string Url 
    { 
     get 
     { 
      return this.strUrl_m; 
     } 
    } 

    private bool bHandled_m; 
    /// <summary> 
    /// Determines if the exception should be considered handled 
    /// </summary> 
    public bool Handled 
    { 
     get 
     { 
      return this.bHandled_m; 
     } 
    } 
} 

/// <summary> 
/// The ErrorBehaviorAttribute exists as a mechanism to register an error handler with a service. 
/// This attribute takes a single type parameter. That type should implement the IErrorHandler 
/// interface and should have a public, empty constructor. The attribute then instantiates an 
/// instance of that error handler type and installs it into the service. It does this by 
/// implementing the IServiceBehavior interface and then using the ApplyDispatchBehavior 
/// method to add instances of the error handler to the service. 
/// 
/// To use this class specify the attribute on your service class. 
/// </summary> 
public class ErrorBehaviorAttribute : Attribute, IServiceBehavior 
{ 
    private Type typeErrorHandler_m; 

    public ErrorBehaviorAttribute(Type typeErrorHandler) 
    { 
     this.typeErrorHandler_m = typeErrorHandler; 
    } 

    public ErrorBehaviorAttribute(Type typeErrorHandler, string strUrl, bool bHandled) 
     : this(typeErrorHandler) 
    { 
     this.strUrl_m = strUrl; 
     this.bHandled_m = bHandled; 
    } 

    public virtual void Validate(ServiceDescription description, ServiceHostBase serviceHostBase) 
    { 
     return; 
    } 

    public virtual void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters) 
    { 
     return; 
    } 

    protected virtual IErrorHandler CreateTypeHandler() 
    { 
     IErrorHandler typeErrorHandler; 

     try 
     { 
      typeErrorHandler = (IErrorHandler)Activator.CreateInstance(this.typeErrorHandler_m, this.strUrl_m, bHandled_m); 
     } 
     catch (MissingMethodException e) 
     { 
      throw new ArgumentException("The ErrorHandler type specified in the ErrorBehaviorAttribute constructor must have a public constructor with string parameter and bool parameter.", e); 
     } 
     catch (InvalidCastException e) 
     { 
      throw new ArgumentException("The ErrorHandler type specified in the ErrorBehaviorAttribute constructor must implement System.ServiceModel.Dispatcher.IErrorHandler.", e); 
     } 

     return typeErrorHandler; 
    } 

    public virtual void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase) 
    { 
     IErrorHandler typeErrorHandler = this.CreateTypeHandler();    

     foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers) 
     { 
      ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher; 
      channelDispatcher.ErrorHandlers.Add(typeErrorHandler); 
     } 
    } 

    private string strUrl_m; 
    /// <summary> 
    /// Specifies a url of the service, displayed in the error log 
    /// </summary> 
    public string Url 
    { 
     get 
     { 
      return this.strUrl_m; 
     } 
    } 

    private bool bHandled_m; 
    /// <summary> 
    /// Determines if the ServiceErrorHandler will consider the exception handled 
    /// </summary> 
    public bool Handled 
    { 
     get 
     { 
      return this.bHandled_m; 
     } 
    } 
} 

Servicio:

[ServiceContract(Namespace = "http://example.come/test")] 
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] 
[ErrorBehavior(typeof(ServiceErrorHandler),"ExceptonTest.svc",false)] 
public class ExceptonTest 
{ 
    [OperationContract] 
    public void TestException() 
    { 
     throw new Exception("this is a test!"); 
    } 
} 
+5

Solo como sugerencia, no complete su código con comentarios. Como SO permite entremezclar el código y el texto, úselo para que las personas no tengan que desplazarse por un bloque de código relativamente pequeño para ver lo que está haciendo. –

6

Le sugiero que centralice el manejo de errores de su servicio WCF en lugar de poner try/catch en cada método. Para ello se puede implementar la interfaz IErrorHandler:

public class ErrorHandler : IErrorHandler 
{ 
    public bool HandleError(Exception error) 
    { 
     return true; 
    } 

    public void ProvideFault(Exception error, MessageVersion version, ref Message msg) 
    { 
     DataAccessFaultContract dafc = new DataAccessFaultContract(error.Message); 
     var fe = new FaultException<DataAccessFaultContract>(dafc); 
     Message fault = fe.CreateMessageFault(); 
     string ns = "http://www.example.com/services/FaultContracts/DataAccessFault"; 
     msg = Message.CreateMessage(version, fault, ns); 
    } 
} 

El método ProvideFault se llama cada vez que uno de sus OperationContract se produce una excepción. Convertirá la excepción en un FaultContract personalizado y se lo enviará al cliente. De esta manera ya no es necesario poner try/catch en cada método. También puede enviar un FaultContract diferente dependiendo de la excepción lanzada.

En el lado del cliente necesita capturar FaultException<DataAccessFaultContract> cada vez que llama a un método en su servicio web.

+7

Ok, así que, ¿qué hacer con la clase ManejadorError entonces? ¿Cómo lo asocias a tu servicio? – Jeremy

-6

Para los perezosos (como yo):

using System.ServiceModel; 
using System.ServiceModel.Dispatcher; 
using System.ServiceModel.Description;