- Description du contexte Cas d'école ... Nous avons une table "blog" qui est lié en relation 1..N a la table "Post".
- Description de l'interface du service WCF Le contrat du service WCF a deux méthodes qui demandent obligatoirement une transaction (précisé via l'attribut TransactionFlowAttribute).
- Implémentation concrète du serveur WCF 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.
- Programme console de lancement du service WCF
- Fichier de configuration du service WCF 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...
- Client consommant le service WCF
- Cas 1 : Réussite -> Tous les champs sont remplis correctement. Les tuples peuvent s'enregistrer en DB.
- Cas 2 : Echec -> La propriété Message sur Post est obligatoire ... L'insertion va donc échouer ... Il faut rollback l'insertion du blog, etc.
- Conclusion J'espère avoir pu vous aider dans l'approche des transactions distribuées sous WCF. Bonne soirée ;-)
[ServiceContract] public interface InterfaceSrv { [OperationContract] [TransactionFlow(TransactionFlowOption.Mandatory)] int AddBlog(Blog blog); [OperationContract] [TransactionFlow(TransactionFlowOption.Mandatory)] int AddPost(Post post); }
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 }
class Program { static void Main(string[] args) { using (ServiceHost hote = new ServiceHost(typeof(ClassServer))) { hote.Open(); Console.WriteLine("Service démarré"); Console.ReadLine(); } } }
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>
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"); }
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"); }