1. Description du contexte
  2. Cas d'école ... Nous avons une table "blog" qui est lié en relation 1..N a la table "Post".
  3. Description de l'interface du service WCF
  4. Le contrat du service WCF a deux méthodes qui demandent obligatoirement une transaction (précisé via l'attribut TransactionFlowAttribute).
    [ServiceContract]
    public interface InterfaceSrv
    {
        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        int AddBlog(Blog blog);
    
        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        int AddPost(Post post);
    }
    
  5. Implémentation concrète du serveur WCF
  6. Il ne suffit pas de manipuler un fichier XML pour configurer le service et de tag les méthodes avec l'attribut TransactionFlow pour qu'une application client supporte les transactions distribuées. En effet, il faut positionner la propriété TransactionScopeRequired à "true" sur les méthodes. Il est également important de préciser qu'il est "interdit" d'avoir des méthodes OneWay avec le support des transactions distribuées ... Il est également important de ne pas oublier que certains protocoles supportés par WCF ne supportent pas les transactions distribuées.
    public class ClassServer : InterfaceSrv
    {
        #region InterfaceSrv Members
    
        [OperationBehavior(TransactionScopeRequired = true)]
        public int AddBlog(Blog blog)
        {
            // Ouvre le contexte de Linq To SQL
            using (EntitiesDataContext context = new EntitiesDataContext())
            {
                context.Blogs.InsertOnSubmit(blog);
                context.SubmitChanges();
                return blog.ID;
            }
        }
    
        [OperationBehavior(TransactionScopeRequired = true)]
        public int AddPost(Post post)
        {
            // Ouvre le contexte de Linq To SQL
            using (EntitiesDataContext context = new EntitiesDataContext())
            {
                context.Posts.InsertOnSubmit(post);
                context.SubmitChanges();
                return post.ID;
            }
        }
    
        #endregion
    }
    
  7. Programme console de lancement du service WCF
  8. class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost hote = new ServiceHost(typeof(ClassServer)))
            {
                hote.Open();
                Console.WriteLine("Service démarré");
                Console.ReadLine();
            }
        }
    }
    
  9. Fichier de configuration du service WCF
  10. Le fichier de configuration du service WCF a dans le serviceBehavior l'attribut "includeExceptionDetailInFaults" à true. Cette option offre le passage d'une exception du service WCF vers le client qui le consomme. Ainsi, il est possible de connaître exactement l'erreur qui s'est produite. Cette option n'est pas à utiliser en production !!!!. De même, il n'est pas nécessaire d'avoir la publication (par exemple le fichier WSDL de description) du service WCF activé en production...
    WS-Transaction ne demande pas WS-Reliable activé. Si un paquet s'est perdu et que la transaction échoue/timeout, il y aura une exception... Cependant, il est conseillé de garantir le transport des données via WS-Reliable.
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>
        <services>
          <service name = "ServerWCF.ClassServer" behaviorConfiguration="behavior">
            <endpoint
               address  = "http://localhost:8000/SvrDemo"
               binding  = "wsHttpBinding"
               bindingConfiguration = "BindingHttp"
               contract = "ServerWCF.InterfaceSrv"
             />
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost:8000/SvrDemo" />
              </baseAddresses>
            </host>
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior name="behavior">
              <serviceTimeouts transactionTimeout="00:00:30"/>
              <serviceDebug includeExceptionDetailInFaults="true"/>
              <serviceMetadata httpGetEnabled="true" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <bindings>
          <wsHttpBinding>
            <binding name="BindingHttp" transactionFlow="true">
              <reliableSession enabled = "true"/>
            </binding>
          </wsHttpBinding>
        </bindings>
      </system.serviceModel>
    </configuration>
    
  11. Client consommant le service WCF
    • Cas 1 : Réussite -> Tous les champs sont remplis correctement. Les tuples peuvent s'enregistrer en DB.
    • try
      {
          // Va réussir !
          using (TransactionScope tx = new TransactionScope())
          {
              InterfaceSrvClient client
                  = new InterfaceSrvClient();
              Blog blog = new Blog();
              blog.Title = "Blabla";
              blog.ID = client.AddBlog(blog);
      
              Post post = new Post();
              post.BlogID = blog.ID;
              post.Date = DateTime.Now;
              post.Message = "Un message";
              post.ID = client.AddPost(post);
      
              tx.Complete();
          }
          Console.Out.WriteLine("Fini 1");
      }
      catch { Console.Out.WriteLine("Oops 1"); }
      
    • Cas 2 : Echec -> La propriété Message sur Post est obligatoire ... L'insertion va donc échouer ... Il faut rollback l'insertion du blog, etc.
    • try
      {
          // Va échouer !!!
          using (TransactionScope tx = new TransactionScope())
          {
              InterfaceSrvClient client
                  = new InterfaceSrvClient();
              Blog blog = new Blog();
              blog.Title = "Blabla";
              blog.ID = client.AddBlog(blog);
      
              Post post = new Post();
              post.BlogID = blog.ID;
              post.Date = DateTime.Now;
              post.ID = client.AddPost(post);
      
              tx.Complete();
          }
          Console.Out.WriteLine("Fini 2");
      }
      catch { Console.Out.WriteLine("Oops 2"); }
      
  12. Conclusion
  13. J'espère avoir pu vous aider dans l'approche des transactions distribuées sous WCF. Bonne soirée ;-)