Silverlight+WPF

Blog d'Alexandre Arnaudet et de ses collègues chez CLT-Services autour de WPF, de Silverlight et des RIA

Recent posts

Tags

Categories

Navigation

Pages

    Archive

    Blogroll

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    Fault Exception avec WCF / Silverlight

    Par défaut, lorsqu'une exception est levée dans une méthode d'un service, le contenu de l'exception n'est pas envoyé au client qui consomme le service.

    Lors de la phase de développement, une pratique commune est de modifier la behavior associée au service en ajoutant l'élément serviceDebugincludeExceptionDetailInFaults à true pour que les erreurs puissent remonter jusqu'au client. Bien entendu, n'oubliez pas de désactiver cette option lors de la livraison de votre service en production.

    Si vous avez fait attention, vous aurez remarqué que le dernier mot de l'élément cité ci-dessus est Fault.  Une Fault est une classe sérialisable permettant de faire transiter entre le serveur et le client des informations structurées concernant l'erreur qui est arrivée.

    Pour utiliser les Faults, il vous suffit d'ajouter l'attribut FaultContract sur une opération identifiée par l'attribut OperationContract et d'y passer le type de fault gérée. A noter qu'on peut renvoyer des Faults aussi bien technique que métier.

    Codons un exemple pour illustrer ce que nous venons de dire.

    Commencez par créer une solution et ajoutez-y une librairie WCF. Dans cette exemple nous allons considérer que notre service a une seule opération permettant de retourner un customer.

    L'élément doit être sérialisable et est représenté par les trois propriétés suivantes:

    [DataContract]
       public class Customer
       {
           [DataMember]
           public int Id { get; set; }
           [DataMember]
           public string LastName { get; set; }
           [DataMember]
           public string FirstName { get; set; }
       }

    Pour définir le service, nous allons dans un premier temps créer le contrat et y ajouter l'opération GetCustomer

    [ServiceContract]
       public interface ICustomerService
       {
           [OperationContract]
           [FaultContract(typeof(CustomerNotFoundFault))]
           Customer GetCustomer(int projectId);
       }

    Ensuite ajoutez une implémentation pour ce service

    public class CustomerService : ICustomerService
       {
           private List<Customer> Customers = new List<Customer>();
    
           public CustomerService()
           {
               Customers.Add(
                   new Customer
                   {
                       Id = 1,
                       FirstName = "FirstName1",
                       LastName = "LastName1"
                   });
               Customers.Add(
                   new Customer
                   {
                       Id = 2,
                       FirstName = "FirstName2",
                       LastName = "LastName2"
                   });
           }
    
           public Customer GetCustomer(int projectId)
           {
               throw new Exception("test exception");
           }
       }

    Le service contient une collection de customer dont nous nous servirons plus tard. Bien entendu dans un projet réel, vous aurez une DAL. J'ai tout codé dans le service pour des raisons de simplicité pour la démo.

    Afin de tester que par défaut aucune information sur le contenu de l'exception est envoyée au client, j'ai ajouté un throw new Exception.

    Ajoutez une application console pour tester le service, puis créez le proxy et appelez l'opération GetCustomer

    static void Main(string[] args)
            {
                CustomerServiceClient client = new CustomerServiceClient();
                client.GetCustomer(-1);
            }

    Vous devriez obtenir l'écran suivant:

    image

    On se rend compte qu'une FaultException générique a été créée par WCF pour nous suite à une exception sur le serveur. Cependant nous n'avons aucune information détaillée sur la raison réelle de l'exception.

    En effet, si vous jetez une petit coup d'œil dans le fichier de configuration serveur, vous remarquerez que includeExceptionDetailInFault est à false.

     <serviceDebug includeExceptionDetailInFaults="False" />
    

    Pour aller jusqu'au bout de ce test, changez la valeur en la passant à true et exécutez à nouveau l'application console.

    image

    Cette fois, le contenu de l'exception a bien été envoyé au client. Bien entendu pour une application destinée à être utilisée en production nous allons laisser cette valeur à false et utiliser des Faults dites déclarées.

    Mettons à jour le code pour refléter l'utilisation des Faults. Pour cela posons-nous la question suivante :

    Quelles sont les erreurs pouvant être levées lors de l'appel de la méthode GetCustomer ? Pour ne pas compliquer les choses nous allons nous limiter à:

    • Id customer non valide
    • customer non trouvé.

    Pour matérialiser ces deux points nous allons utiliser les classes FaultException pour l'Id non valide, et une FaultException personnalisée que nous nommerons CustomerNotFoundFault pour l'utilisateur non trouvé.

    FaultException est nativement gérée par WCF. Pour indiquer qu'une CustomerNotFoundFault peut être levée dans l'opération, nous devons ajouter l'attribut FaultContract au niveau de la déclaration de l'opération.

        [OperationContract]
        [FaultContract(typeof(CustomerNotFoundFault))]
        Customer GetCustomer(int projectId);
    [DataContract]
       public class CustomerNotFoundFault
       {
           [DataMember]
           public string ErrorMessage { get; set; }
    
           [DataMember]
           public int Id { get; set; }
       }

    Si vous omettez cet attribut, ce type de FaultException ne sera tout simplement pas exploitable par le client du service.

    Pour résumer ces quelques lignes voici le code correspondant:

    public Customer GetCustomer(int Id)
     {
        FaultCode faultCode;
        FaultReason faultReason;
    
        Customer cust = null;
    
        if (Id < 1)
        {
           faultReason = new FaultReason("Invalid Customer Id");
           faultCode = FaultCode
              .CreateSenderFaultCode("InvalidRequest", this.GetType().FullName);
               throw new FaultException(faultReason, faultCode);
        }
    
    
        cust = Customers.FirstOrDefault(c => c.Id == Id);
        if (cust == null)
        {
         CustomerNotFoundFault fault = new CustomerNotFoundFault
         {
           Id = Id,
           ErrorMessage = string.Format("No Customer with id {0}", Id)
         };
             throw new
              FaultException<CustomerNotFoundFault>(fault, fault.ErrorMessage);
        }
    
        return cust;
     }

    Vous constaterez également l'utilisation de FaultReason permettant de décrire l'erreur et FaultCode plus utilisé pour identifier la source, le type de l'erreur.

    Pour voir maintenant ce qui arrive côté client, effectuons de nouveaux tests, avec pour commencer, une valeur inférieure à 0.

    image

    On voit que cette fois-ci le client reçoit une FaultException. Au niveau du contenu, on retrouve la FaultReason ainsi que le Fault Code qui ont été spécifiés côté serveur.

    Dans le code ci-dessous, on voit que la propriété IsSenderFault est à true. En effet le serveur a précisé qu'il s'agissait d'une erreur due au client. En l'occurrence, il s'agit ici de l'id customer qui n'est pas valide.

    image

    Pour la seconde erreur, à savoir un customer non trouvé, nous venons de voir qu'une exception FaultException<CustomerNotFoundFault> était levée.

    Coté client il vous suffit de mettre un try / catch englobant l'appel au service avec une gestion d'erreur du plus spécifique au plus général, c'est à dire d'abord  FaultException<CustomerNotFoundFault> puis  FaultException. Une fois l'erreur attrapée vous pourrez prendre  une décision sur le comportement que le client doit avoir.

    Nous venons de voir globalement comment fonctionnaient les FaultException avec WCF et une application cliente de type console.

    Dans le cadre d'un client comme silverlight, quelques développements supplémentaires sont nécessaires.

    En effet par défaut lorsqu'une erreur arrive côté serveur une erreur 500 est envoyée au client. Il sait qu'il y  a eu une erreur mais sans plus de détail.

    Le message exacte est :" The remote server returned an error: NotFound"

    image

    Une solution possible est donc d'intercepter côté serveur toute Fault et de modifier le code 500 renvoyé au client par un code 200 qui dans le cadre d'une réponse HTTP signifie que tout s'est bien passé. Le client Silverlight pourra ainsi accéder à la Fault.

    Pour y parvenir, nous allons créer une behavior qui sera associée au EndPoint. Cette behavior doit hériter de BehaviorExtensionElement et implémenter l'interface IEndPointBehavior.

    • BehaviorExtensionElement représente un élément de configuration pour paramétrer une extension
    • IEndPointBehavior apporte des méthodes pour modifier le comportement du EndPoint

    Parmi les méthodes disponibles via l'interface IEndPointBehavior, une nous intéresse plus particulièrement ici. Il s'agit de la méthode ApplyDispatcherBehavior qui va nous permettre d'étendre la manière dont réagit le service.

    public class FaultBehaviour : BehaviorExtensionElement, IEndpointBehavior
       {
           public void ApplyDispatchBehavior
               (ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
           {
               SilverlightFaultMessageInspector inspector =
                   new SilverlightFaultMessageInspector();
               endpointDispatcher
                   .DispatchRuntime.MessageInspectors.Add(inspector);
           }

    Dans cette méthode on retrouve la création d'une classe implémentant l'interface IDispatchMessageInspector dont le but est comme son nom le laisse entendre, d'intercepter le message transitant pour l'analyser et / ou le modifier.

    Rappelons que nous souhaitons modifier le code http renvoyé au client. Pour cela implémentons la méthode BeforeSendReply de IDispatchMessageInspector.

    public void BeforeSendReply(ref Message reply, object correlationState)
     {
      if (reply.IsFault)
      {
       HttpResponseMessageProperty property = new HttpResponseMessageProperty();
    
                       // Change response code to 200 (default: 500)
          property.StatusCode = System.Net.HttpStatusCode.OK;
    
          reply.Properties[HttpResponseMessageProperty.Name] = property;
       }
      }

    Dans ce code, nous voyons clairement, que si la réponse est une FaultException, nous passons le code de status à Ok: HTTP 200.

    Afin que cette extension puisse être utilisée, il nous reste un dernier point à voir. Mettre à jour le fichier de configuration, en ajoutant l'extension et en l'assignant au endpoint correspondant.

    Pour cela dans la section ServiceModel du web.config, commencez par déclarer l'extension.

    <extensions>
          <behaviorExtensions>
            <add name="silverlightFaults"
                 type="SilverlightApplicationFault.Web.FaultBehaviour, 
                      SilverlightApplicationFault.Web,
                      Version=1.0.0.0, 
                      Culture=neutral,
                      PublicKeyToken=null"/>
          </behaviorExtensions>
        </extensions>
    

    Ajoutez ensuite la behavior dans la section behaviors:

     <endpointBehaviors>
            <behavior name="SilverlightFaultBehavior">
              <silverlightFaults/>
            </behavior>
          </endpointBehaviors>
    

    Enfin reconfigurez le endpoint pour utiliser la behavior que l'on vient d'ajouter.

      <endpoint address="" binding="customBinding"
        bindingConfiguration="SilverlightApplicationFault.Web
          .CustomerService.customBinding0"
        contract="SilverlightApplicationFault.Web.ICustomerService"
        behaviorConfiguration="SilverlightFaultBehavior" />
    

    La configuration est à présent terminée.

    Relancez l'application, vous devriez voir la Fault remontée côté client.

    image

    Vous pouvez télécharger les sources ici.

    Posted: Dec 18 2010, 18:02 by Alexandre Arnaudet | Comments (0) RSS comment feed |
    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5
    Filed under: Silverlight | WCF