2009-01-14 18 views
5

Próbuję wymyślić prosty, łatwy w użyciu wzorca projektowego za obsługę w usłudze .net wcf silverlight (konkretnie włączona usług WCF) błędu. Jeśli wyjątek zostanie zgłoszony w metodzie serwisowej, aplikacja silverlight zobaczy komunikat CommunicationException stwierdzający "Serwer zdalny zwrócił błąd: NotFound --->" i prawdopodobnie ślad stosu w zależności od ustawień, co jest całkowicie nieprzydatne, ponieważ nie robi tego ". t powiedzieć rzeczywisty błąd, a zwykle prawdziwy błąd nie jest "NotFound"..Net WFC/serwis internetowy obsługa wyjątków wzornictwo

czytania na usługach internetowych i usług WCF i wyjątki, trzeba wyrzucić mydło/WCF standardowych wyjątkami takimi jak FaultException lub SoapException. Tak więc dla usługi wcf należy zawijać każdą metodę w próbie catch, przechwycić każdy wyjątek, zawinąć go w wyjątek FaultException i rzucić. Przynajmniej to jest moje zrozumienie, popraw mnie, jeśli się mylę.

Więc stworzyłem mój projekt wzoru:

[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); 
     } 
    } 
} 

tak aby długie opowiadanie, ja wciąż nie prawidłowy błąd w mojej aplikacji Silverlight. Sprawdzam obiekt AsyncCompletedEventArgs.Error i nadal zawiera obiekt CommunicationException z ogólnym błędem. Pomóż mi wymyślić ładny prosty wzór, który pozwoli mi z łatwością rzucić poprawny wyjątek z usługi i łatwo go złapać w aplikacji.

Odpowiedz

5

Ok, zajrzałem do idei IErrorHandler. Nie miałem pojęcia, że ​​możesz to zrobić w ten sposób i jest to idealne rozwiązanie, ponieważ pozwala uniknąć prób przyłowów dla każdej metody. Czy możesz to zrobić również w standardowych usługach sieciowych? I wprowadziły go w następujący sposób:

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

usługę:

[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

Podobnie jak sugestię, nie wypełniaj swojego kodu komentarzami. Ponieważ SO pozwala na mieszanie kodu i tekstu, użyj go, aby ludzie nie musieli przeglądać względnie małego bloku kodu, aby zobaczyć, co robisz. –

6

Sugerowałbym scentralizować obsługę błędów z usługą WCF zamiast oddanie try/catch na każdej metody. Aby to zrobić, można zaimplementować interfejs 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); 
    } 
} 

Sposób ProvideFault nazywa kiedy jeden z twoich OperationContract zgłasza wyjątek. Przekształci on wyjątek w niestandardowo zdefiniowany FaultContract i wyśle ​​go do klienta. W ten sposób nie musisz już umieszczać try/catch w każdej metodzie. Możesz również wysłać inny numer FaultContract w zależności od wygenerowanego wyjątku.

Po stronie klienta trzeba złapać FaultException<DataAccessFaultContract> każdym razem wywołać metodę na usługi internetowej.

+7

Ok, więc co zrobić z klasą ErrorHandler wtedy? Jak kojarzysz to ze swoją usługą? – Jeremy

-6

dla leniwych (jak ja):

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