Les esprits mobiles ne sont pas garantis contre les idées fixes.
[Commentaire(s)] [PDF] [Imprimable]
Web service SOAP avec WCF et LINQ
- Ecrit par : Hyacinthe MENIET
- Créé le : 2010-02-06 10:15:37
- Dernière maj : 2010-02-06 10:15:37
- Profil : Utilisateur avancé
Les Web services SOAP sont un moyen d'interopérer avec des programmes écrits dans des langages différents ou qui s'exécutent sur des machines différentes. Ce document décrit les concepts et les étapes pour exposer sous forme de Web service SOAP des données SQL sérialisés via LINQ (Language Integrated Query). Dans ce chapitre, j'aborderai les notions de base dont vous aurez besoin pour comprendre et travailler avec les services WCF (Windows Communication Foundation). WCF étant l'implémentation par Microsoft d'un ensemble de normes qui définissent les interactions entre services, la (dé-)sérialisation d'objets et la gestion des protocoles associés.
1. Pré-requis
- Vous êtes familier de C# 3.0 et de sa syntaxe.
- Vous connaissez SOAP.
- Vous avez installé IIS 7 et sa console de gestion (inetmgr).
- Vous avez installé - à minima - et êtes familier de Microsoft SQL Server 2008 Express SP1.
- Pour vous connecter à SQL Server 2008 Express vous utilisez l'Authentification SQL Server et non l'Authentification Windows.
- Vous avez installé - à minima - et êtes familier de Microsoft Visual Web Developer 2008 Express SP1.
- Vous avez installé - à minima - et êtes familier de Microsoft Visual C# 2008 Express.
2. Vue d'ensemble
2.1 Présentation de WCF
WCF fournit aux développeurs l'essentiel pour développer, déployer, exécuter et consommer des services sous Windows. Cela inclus, l'hébergement, la gestion d'instance de service, les appels asynchrones, la fiabilité, la gestion des transactions, la gestion des files d'attente et la sécurité. WCF est un composant de Microsoft .NET 3.5. La plupart des fonctionnalités de WCF sont incluses dans l'assembly System.ServiceModel.dll et accessibles depuis le namespace System.ServiceModel.
WCF supporte plusieurs bindings :
| Binding | Description | Transport | Codage | Inter? |
|---|---|---|---|---|
| Basic binding | Ce binding est dédié aux clients et services basés sur ASMX,ou qui respectent le profil WS-I Basic Profile 1.1. | HTTP/HTTPS | Texte, MTOM | Oui |
| TCP binding | Binding sécurisée et fiable, optimisé pour les communications WCF-to-WCF. | TCP | Binaire | Non |
| IPC binding | Offre des fonctionnalités équivalentes à NetTcpBinding à ceci près qu'il est limité à la communication inter-processus (donc sur la même machine). C'est le plus performant. | IPC | Binaire | Non |
| Web Service (WS) binding | Ce binding est conçu pour interopérer avec tout framework prennant en charge la spécification WS-* (sessions fiables, transactions distribuées et sécurité). | HTTP/HTTPS | Texte, MTOM | Oui |
| Dual WS binding | Similaire au WSHttpBinding, de plus, il supporte les contrats de service duplex qui permettent aux services et clients d'envoyer et recevoir des messages. | HTTP | Texte, MTOM | Non |
| MSMQ binding | Ce binding est dédié aux communications avec file d'attente. | MSMQ | Binaire | Non |
Ce tutoriel est consacré à SOAP, c'est pourquoi je me concentrerai sur Web Service (WS) binding.
Dans WCF, tous les services exposent des contrats. Les contrats étant le moyen standard pour décrire ce que fait le service. WCF définit 4 types de contrats:
- Contrat de services: Décrit les opérations que le client peut effectuer via le service.
- Contrat de données: Définit de quels types sont les données qui sont passées depuis et vers le service. WCF définit des contrats implicites pour les types primitifs.
- Contrat d'erreurs: Précise les erreurs qui sont soulevées par le service et la façon dont le service gère les erreurs et les propage à ses clients.
- Contrat de message: Les contrats de message peuvent être dactylographiés ou non typé et sont utiles pour interopérer avec des services qui utilisent des formats propriétaires de message.
Enfin, chaque service WCF doit être hébergé dans un processus Windows appelé processus hôte. Un unique processus hôte peut héberger plusieurs services et le même service peut être hébergé dans de multiples processus hôtes. WCF supporte les processus hôtes suivants:
- Internet Information Services (IIS) 5/6 : Limités aux services transportés via HTTP(S).
- Self-hosting (Auto-hébergement) : Dans ce cas, le processus hôte est une application Windows Forms, une application console, ou un service NT. Ce mode d'hébergement supporte tous les types de transport pris en charge par WCF.
- Internet Information Services (IIS) 7 : Il supporte tous les types de transport pris en charge par WCF.
2.2 Présentation rapide de LINQ
LINQ est l'une des nouvelles technologies livrées avec Microsoft .NET 3.5. LINQ permet d'interroger des sources de données directement en C#. Traditionnellement, pour interroger des données structurées le programmeur devait passer par un langage externe comme SQL ou XPath. Avec LINQ cette étape n'est plus nécessaire.
LINQ supporte plusieurs type de sources de données, notamment les bases de données SQL, les documents XML, les DataSet ADO.NET et les collections d'objets en mémoire. LINQ to SQL fait référence aux capacités ORM (object-relational mapping) de LINQ.
2.3 Présentation de l'article
Pour rendre ce tutoriel digeste, je vais parcourir les capacités de WCF et LINQ à travers l'exemple d'un logiciel de gestion de finances personnelles de type Microsoft Money. Ce logiciel sera multi-comptes et utilisable à distance via son API Web services SOAP. Les 6 opérations exposées sont les suivantes :
- Lister les comptes
- Lister les opérations sur un compte.
- Récupérer le solde d'un compte.
- Retirer de l'argent d'un compte.
- Verser de l'argent sur un compte.
- Transférer de l'argent d'un compte vers un autre.
Vous pouvez télécharger le code source du projet WCF et LINQ pour Visual Web Developer 2008 Express et Visual C# 2008 Express.
3. Le Web service
3.1 La base de données
Créez la base de données Banking contenant les 2 tables Operation et Account suivantes:

J'ai réalisé ce schéma avec Microsoft SQL Server Management Studio 2008 Express. Cette capture correspond au schéma de données suivant:
USE [Banking] GO /****** Object: Table [dbo].[Account] Script Date: 02/04/2010 13:54:16 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[Account]( [id] [int] IDENTITY(1,1) NOT NULL, [name] [nvarchar](max) NOT NULL, [initial_balance] [decimal](18, 0) NOT NULL, [date_creation] [datetime] NOT NULL, CONSTRAINT [PK_Account] PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO /****** Object: Table [dbo].[Operation] Script Date: 02/04/2010 13:54:16 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[Operation]( [id] [int] IDENTITY(1,1) NOT NULL, [amount] [decimal](18, 0) NOT NULL, [date_insertion] [datetime] NOT NULL, [account_id] [int] NOT NULL, CONSTRAINT [PK_Operation] PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO /****** Object: ForeignKey [FK_Operation_Account] Script Date: 02/04/2010 13:54:16 ******/ ALTER TABLE [dbo].[Operation] WITH CHECK ADD CONSTRAINT [FK_Operation_Account] FOREIGN KEY([account_id]) REFERENCES [dbo].[Account] ([id]) GO ALTER TABLE [dbo].[Operation] CHECK CONSTRAINT [FK_Operation_Account] GO
Initialisez la table Account avec les données ci-dessous:

3.2 La couche persistance
Dans Visual Web Developer, créez un projet Application du service WCF nommée Dotmyself. Ajoutez un nouvel élément de type Classes LINQ to SQL nommé Banking.dbml à votre projet. Cela ouvre l'outil Concepteur Objet/Relationnel. Depuis l'Explorateur de bases de données, connectez-vous à la base de données Banking :

DEMETER est le nom du serveur sur lequel est installé mon SQL Server 2008 Express.
Faîtes glisser déposer, dans l'outil Concepteur Objet/Relationnel, les tables Operation et Account. Après modification des entités générées vous devriez obtenir ceci :

Assurez-vous que votre BankingDataContext ressemble à ceci:

Enfin, éditez l'association entre Operation et Account, là ajoutez un « s » à Operation s'il n'en a pas

3.3 Encapsulation de la couche persistance
Créez la classe DataManager.cs qui offre un accès générique à la couche persistance :
using System.Linq;
using System.Linq.Expressions;
using System.Data.Linq.Mapping;
using System;
using System.Collections.Generic;
namespace Dotmyself
{
/// <summary>
/// It offers a generic CRUD abstraction for entities.
/// Author : Hyacinthe MENIET
/// Date : 02/04/2010
/// </summary>
/// <typeparam name="T">
/// Entity that a client specifies when it instantiates this class.
/// </typeparam>
public class DataManager<T> where T : class
{
private readonly BankingDataContext context;
public DataManager(BankingDataContext dc)
{
context = dc;
}
public virtual IEnumerable<T> List()
{
return context.GetTable<T>();
}
public virtual T Get(int id)
{
MetaTable mapping = context.Mapping.GetTable(typeof(T));
MetaDataMember pkfield = mapping.RowType.DataMembers.SingleOrDefault(
d => d.IsPrimaryKey);
ParameterExpression param = Expression.Parameter(typeof(T), "e");
var p = Expression.Lambda<Func<T, bool>>(
Expression.Equal(Expression.Property(param, pkfield.Name),
Expression.Constant(id)),
new ParameterExpression[] { param });
return context.GetTable<T>().SingleOrDefault(p);
}
public virtual void Create(T entity)
{
context.GetTable<T>().InsertOnSubmit(entity);
}
public virtual void Update(T entity)
{
context.GetTable<T>().Attach(entity);
}
public virtual void Delete(T entity)
{
context.GetTable<T>().DeleteOnSubmit(entity);
}
public virtual void Commit()
{
context.SubmitChanges();
}
}
}
3.4 La couche service
Créez le contrat de service IBankingService.cs:
using System.ServiceModel;
using System.Collections.Generic;
namespace Dotmyself
{
/// <summary>
/// It defines the capability and feature set, offered by the Banking Service.
/// Author : Hyacinthe MENIET
/// Date : 02/04/2010
/// </summary>
[ServiceContract (Namespace = "http://www.dotmyself.net/BankingService/2010/02")]
public interface IBankingService
{
[OperationContract]
IList<Account> GetAccounts();
[OperationContract]
IList<Operation> GetOperationsByAccount(int aId);
[OperationContract]
decimal GetBalance(int aId);
[OperationContract]
void withdrawMoney(int aId, decimal amount);
[OperationContract]
void depositMoney(int aId, decimal amount);
[OperationContract]
void transferMoney(int fromId, int toId, decimal amount);
}
}
Puis son implémentation BankingService.svc.cs:
using System.Collections.Generic;
using System.Linq;
using System;
using System.Configuration;
namespace Dotmyself
{
/// <summary>
/// It implements the operations the client can perform on the Banking Service.
/// Author : Hyacinthe MENIET
/// Date : 02/04/2010
/// </summary>
public class BankingService : IBankingService
{
private readonly DataManager<Account> accountManager;
private readonly DataManager<Operation> operationManager;
public BankingService()
{
string ConnectStr = ConfigurationManager.ConnectionStrings["BankingConnectionString"].ConnectionString;
BankingDataContext context = new BankingDataContext(ConnectStr);
accountManager = new DataManager<Account>(context);
operationManager = new DataManager<Operation>(context);
}
public IList<Account> GetAccounts()
{
return accountManager.List().ToList();
}
public IList<Operation> GetOperationsByAccount(int aId)
{
Account ac = accountManager.Get(aId);
return ac.Operations.ToList();
}
public decimal GetBalance(int aId)
{
Account ac = accountManager.Get(aId);
decimal sumOp = ac.Operations.Sum(o => o.Amount);
return sumOp + ac.InitialBalance;
}
public void withdrawMoney(int aId, decimal amount)
{
Operation op = new Operation
{
Amount = -1 * Math.Abs(amount),
AccountId = aId,
DateInsertion = DateTime.Now
};
operationManager.Create(op);
operationManager.Commit();
}
public void depositMoney(int aId, decimal amount)
{
Operation op = new Operation
{
Amount = Math.Abs(amount),
AccountId = aId,
DateInsertion = DateTime.Now
};
operationManager.Create(op);
operationManager.Commit();
}
public void transferMoney(int fromId, int toId, decimal amount)
{
Operation opFrom = new Operation
{
Amount = -1*Math.Abs(amount),
AccountId = fromId,
DateInsertion = DateTime.Now
};
Operation opTo = new Operation
{
Amount = Math.Abs(amount),
AccountId = toId,
DateInsertion = DateTime.Now
};
operationManager.Create(opFrom);
operationManager.Create(opTo);
operationManager.Commit();
}
}
}
Terminez par le fichier de service BankingService.svc :
<%@ ServiceHost Language="C#" Debug="true" Service="Dotmyself.BankingService" CodeBehind="BankingService.svc.cs" %>
4. Déploiement du Web service sur IIS
4.1 Fichier de configuration
Ecrasez votre Web.config par celui-ci:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="BankingConnectionString" connectionString="Data Source=DEMETER\SQLEXPRESS;Initial Catalog=Banking;Persist Security Info=True;User ID=bkuser;Password=s3cr3t"
providerName="System.Data.SqlClient" />
</connectionStrings>
<system.serviceModel>
<services>
<service behaviorConfiguration="Dotmyself.BankingBehavior" name="Dotmyself.BankingService">
<endpoint address="" binding="wsHttpBinding" contract="Dotmyself.IBankingService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Dotmyself.BankingBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Adaptez la connectionString à ce qu'il y a chez vous.
4.2 Configuration d'IIS 7
Je suppose dans la suite que IIS 7 et son gestionnaire de services Internet (inetmgr) sont installés. Lancez un terminal Windows (cmd) en tant qu'administrateur, allez dans C:\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation et tapez :
Cette commande enregistre la version de WCF et met à jour les scriptmaps dans la métabase d'IIS. Allez dans « Panneau de Configuration > Programmes > Activer ou désactiver des fonctionnalités Windows », dans le menu « Microsoft .NET Framework 3.5 », activez :
- Windows Communication Foundation HTTP Activation
- Windows Communication Foundation Non-HTTP Activation
Validez et fermez.
4.3 Déploiement dans IIS 7
Générez (Build) votre projet Dotmyself. Lancez le Gestionnaire de services Internet (inetmgr) et là ajoutez une nouvelle application au Default Web Site :

Dans Chemin d'accès physique prenez soin d'indiquer le dossier qui contient BankingService.svc. Pour valider que l'application est déployée avec succès, vérifiez que la page des métadonnées s'affiche correctement : http://localhost/Dotmyself/BankingService.svc. De plus, vous obtiendrez le WSDL à l'adresse suivante : http://localhost/Dotmyself/BankingService.svc?wsdl
5. Le client
5.1 Ecriture du client
Lancez Microsoft Visual C# 2008 Express et créez un projet Application console nommé Client. Cliquez droit sur Reference et ajoutez une référence de service depuis l'URL http://localhost/Dotmyself/BankingService.svc. Cliquez sur Allez et remplacez l'espace de nom par Dotmyself. Validez et fermez.
Modifiez la classe Program.cs ainsi :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using Client.Dotmyself;
namespace Client
{
/// <summary>
/// It invokes operations on the Banking Service.
/// Author : Hyacinthe MENIET
/// Date : 04/02/2010
/// </summary>
class Program
{
static void Main(string[] args)
{
BankingServiceClient client = new BankingServiceClient();
Console.WriteLine("List all accounts :");
foreach (Account ac in client.GetAccounts())
{
Console.WriteLine("{Id=" + ac.Id + ",Name=" + ac.Name
+ ",InitialBalance=" + ac.InitialBalance + ",DateCreation="
+ ac.DateCreation + "}");
}
Console.WriteLine("Deposit 20 euros in account 1");
client.depositMoney(1, 20);
Console.WriteLine("Withdraw 10 euros in account 1");
client.withdrawMoney(1, 10);
Console.WriteLine("Transfer 30 euros from account 3 to account 1");
client.transferMoney(3, 1, 30);
Console.WriteLine("List operations on account 1 :");
foreach (Operation op in client.GetOperationsByAccount(1))
{
Console.WriteLine("{Id=" + op.Id + ",Amount=" + op.Amount
+ ",DateInsertion=" + op.DateInsertion + "}");
}
Console.WriteLine("Account 1 balance : " + client.GetBalance(1));
Console.WriteLine("Account 3 balance : " + client.GetBalance(3));
Console.ReadLine();
client.Close();
}
}
}
5.2 Exécution
Compilez le client et exécutez-le, vous devriez obtenir le résultat suivant:
{Id=1,Name=Banque Populaire,InitialBalance=50,DateCreation=04/02/2010 00:00:00}
{Id=2,Name=Societe Generale,InitialBalance=120,DateCreation=04/02/2010 00:00:00}
{Id=3,Name=BNP Paribas,InitialBalance=70,DateCreation=03/02/2010 00:00:00}
Deposit 20 euros in account 1
Withdraw 10 euros in account 1
Transfer 30 euros from account 3 to account 1
List operations on account 1 :
{Id=1,Amount=20,DateInsertion=04/02/2010 18:15:05}
{Id=2,Amount=-10,DateInsertion=04/02/2010 18:15:05}
{Id=4,Amount=30,DateInsertion=04/02/2010 18:15:05}
Account 1 balance : 90
Account 3 balance : 40