Cours VB.Net application web MVC multi-couches pdf


Télécharger Cours VB.Net application web MVC multi-couches pdf

★★★★★★★★★★3.5 étoiles sur 5 basé sur 1 votes.
Votez ce document:

Télécharger aussi :


Cours VB.Net application web MVC multi-couches

...

2.4 Le modèle

Le modèle M du MVC est ici constitué des éléments suivants :

  1. les classes métier
  2. les classes d'accès aux données
  3. la base de données

2.4.1 La base de données

La base de données ne contient qu'une table appelée ARTICLES générée avec les commandes SQL suivantes :

CREATE TABLE ARTICLES (  

ID INTEGER NOT NULL,

NOM VARCHAR(20) NOT NULL,

PRIX NUMERIC(15,2) NOT NULL, STOCKACTUEL INTEGER NOT NULL, STOCKMINIMUM INTEGER NOT NULL

);

/* contraintes */

ALTER TABLE ARTICLES ADD CONSTRAINT CHK_ID check (ID>0); ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRIX check (PRIX>=0);

ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKACTUEL check (STOCKACTUEL>=0); ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKMINIMUM check (STOCKMINIMUM>=0); ALTER TABLE ARTICLES ADD CONSTRAINT CHK_NOM check (NOM<>'');

ALTER TABLE ARTICLES ADD CONSTRAINT UNQ_NOM UNIQUE (NOM);

/* clé primaire */

ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);

clé primaire identifiant un article de façon unique nom de l'article

son prix

stockactuel son stock actuel

stockminimum le stock au-dessous duquel une commande de réapprovisionnement doit être faite

2.4.2 Les espaces de noms du modèle

Le modèle M est fourni sous la forme de deux espaces de noms :

  • istia.st.articles.dao : contient les classes d'accès aux données de la couche [dao]
  • istia.st.articles.domain : contient les classes métier de la couche [domain]

Chacun de ces espaces de noms est contenu au sein d'un fichier " assembly " qui lui est propre :

assembly contenu rôle

webarticles-dao - [IArticlesDao]: l'interface d'accès à la couche [dao]. C'est

la seule interface que voit la couche [domain]. Elle n'en voit pas d'autre.

- [Article] : classe définissant un article

- [ArticlesDaoSqlMap] : classe d'implémentation de l'interface [IArticlesDao] avec une classe utilisant la bibliothèque [Ibatis SqlMap]

couche d'accès aux données - se trouve entièrement dans la couche [dao] de l'architecture 3-tier de l'application web

webarticles-domain - [IArticlesDomain]: l'interface d'accès à la couche

[domain]. C'est la seule interface que voit la couche web. Elle n'en voit pas d'autre.

- [AchatsArticles] : une classe implémentant [IArticlesDomain]

- [Achat] : classe représentant l'achat d'un client

- [Panier] : classe représentant l'ensemble des achats d'un client représente le modèle des achats sur le web - se trouve entièrement dans la couche [domain] de l'architecture 3- tier de l'application web

 3 L'architecture de l'application web [webarticles-part3]

Nous allons construire une application windows qui reprendra l'architecture de l'application web précédente. On référençait cette dernière par [webarticles] Nous référencerons la nouvelle par [webarticles-part3]. Elle présentera l'architecture suivante :

...

  • la couche [ui] est la couche d'interface avec l'utilisateur. Elle implémente le C et le V du modèle MVC. Elle est implémentée par une application windows respectant elle-même l'architecture MVC. Elle s'appuie pour cea sur le moteur MVC [M2VC-win]
  • le modèle M est implémenté par les couches [domain, dao].
  • la couche [ui] n'est pas en contact direct avec le modèle M. Elle passe par le réseau pour dialoguer avec la couche [domain].
  • la couche [domain] est accessible via un service web.

L'application dans son ensemble respecte une architecture MVC (Modèle - Vue - Contrôleur). Si nous reprenons le schéma en couches ci-dessus, l'architecture MVC s'y intègre de la façon suivante :

...

Le fonctionnement de l'ensemble, décrit plus haut page 7, peut être repris ici à l'identique. Il faut simplement se rappeler que le dialogue 2 entre les couches [ui] et [domain] se fait désormais au travers d'un réseau tcp-ip. Les couches [dao] et [domain] et [ui] ont déjà été construites dans les articles 1 à 4. Ce seront pour nous des boîtes noires dont nous rappelons maintenant les caractéristiques principales.

3.1 La couche [dao]

La couche [dao] choisie est celle implémentée par une classe utilisant l'outil [Ibatis SqlMap]. Le lecteur est invité à revoir éventuellement cette implémentation dans l'article 2, paragraphe 8.6. Rappelons-en quelques caractéristiques :

- [IArticlesDao] : l'interface d'accès à la couche [dao]

- [ArticlesDaoSqlMap] : la classe d'implémentation de cette interface

- [Article] : classe définissant un article

La classe définissant un article possède les propriétés publique suivantes :

id - Integer identifiant de l'article

nom - String nom de l'article

prix - Double prix de l'article

stockactuel - Integer stock actuel de l'article

stockminimum - Integer si stockactuel<stockminimum alors il faut réapprovisionner Cette classe offre par ailleurs :

  1. un constructeur permettant de fixer les 5 informations d'un article : [id, nom, prix, stockactuel, stockminimum]
  2. une vérification des données insérées dans l'article. En cas de données erronées, une exception est lancée.
  3. une méthode toString qui permet d'obtenir la valeur d'un article sous forme de chaîne de caractères.

L'interface [IArticlesDao] est définie comme suit :

Imports System

Imports System.Collections Namespace istia.st.articles.dao

Public Interface IArticlesDao ' liste de tous les articles

Function getAllArticles() As IList ' ajoute un article

Function ajouteArticle(ByVal unArticle As Article) As Integer ' supprime un article

Function supprimeArticle(ByVal idArticle As Integer) As Integer ' modifie un article

Function modifieArticle(ByVal unArticle As Article) As Integer ' recherche un article

Function getArticleById(ByVal idArticle As Integer) As Article ' supprime tous les articles

Sub clearAllArticles()

' change le stock d'u article

Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer End Interface

End Namespace

Le rôle des différentes méthodes de l'interface est le suivant :

getAllArticles rend tous les articles de la source de données

clearAllArticles vide la source de données rend l'objet [Article] identifié par son numéro permet d'ajouter un article à la source de données permet de modifier un article de la source de données supprimerArticle permet de supprimer un article de la source de données changerStockArticle permet de modifier le stock d'un article de la source de données

L'interface met à disposition des programmes clients un certain nombre de méthodes définies uniquement par leurs signatures. Elle ne s'occupe pas de la façon dont ces méthodes seront réellement implémentées. Cela amène de la souplesse dans une application. Le programme client fait ses appels sur une interface et non pas sur une implémentation précise de celle-ci.

Le choix d'une implémentation précise se fait au moyen d'un fichier de configuration Spring. L'implémentation [ArticlesDaoSqlMap] choisie ici donne un accès transparent à toutes sortes de bases de données. Par "transparent", nous entendons le fait que changer de SGBD n'a aucune conséquence sur le code. La transparence est obtenue au moyen des fichiers de configuration [articles.xml, properties.xml, providers.config, sqlmap.config] :

  • articles.xml

Ce fichier décrit les commandes SQL a émettre pour obtenir les données nécessaires à la couche [dao] :

<?xml version="1.0" encoding="iso-8859-1" ?>

<sqlMap namespace="Articles" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="SqlMap.xsd">

<!-- les resultMap -->

<resultMaps>

<resultMap id="article" class="istia.st.articles.dao.Article">

<result property="id" column="ID" />

<result property="nom" column="NOM" />

<result property="prix" column="PRIX" />

<result property="stockactuel" column="STOCKACTUEL" />

<result property="stockminimum" column="STOCKMINIMUM" />

</resultMap>

</resultMaps>

<!-- les requêtes SQL -->

<statements>

<!-- obtention de tous les articles -->

<select id="getAllArticles" resultMap="article">

select ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM FROM ARTICLES

</select>

<!-- suppression de tous les articles-->

<delete id="clearAllArticles" resultClass="int"> delete from ARTICLES

</delete>

<!-- insertion d'un article -->

<insert id="insertArticle" parameterClass="istia.st.articles.dao.Article"> insert into ARTICLES (id, nom, prix,stockactuel, stockminimum) values ( #id# , #nom# , #prix# , #stockactuel# , #stockminimum# )

</insert>

<!-- suppression d'un article -->

<delete id="deleteArticle" parameterClass="int" resultClass="int"> delete FROM ARTICLES where ID= #value#

</delete>

<!-- modification d'un article -->

<update id="modifyArticle" parameterClass="istia.st.articles.dao.Article" resultClass="int"> update ARTICLES set NOM= #nom# ,PRIX= #prix# ,STOCKACTUEL= #stockactuel# ,STOCKMINIMUM=

#stockminimum# where ID= #id#

</update>

<!-- recherche d'un article précis -->

<select id="getArticleById" resultMap="article" parameterClass="int">

select ID, NOM, PRIX, STOCKACTUEL, STOCKMINIMUM FROM ARTICLES where ID= #value#

</select>

<!-- changement du stock d'un article -->

<update id="changerStockArticle" parameterClass="Hashtable">

update ARTICLES set STOCKACTUEL=(STOCKACTUEL + #mouvement#) where ID=#id# and ((STOCKACTUEL + #mouvement#) >=0)

</update>

</statements>

</sqlMap>

  • sqlmap.config

Ce fichier configure l'accès aux données :

<?xml version="1.0" encoding="utf-8" ?>

<sqlMapConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Schemas\SqlMapConfig.xsd">

<properties resource="bin/properties.xml"/>

<settings>

<setting useStatementNamespaces="false" />

<setting cacheModelsEnabled="false" />

</settings>

<!-- ==== source de données ========= -->

<database>

<provider name="${provider}"/>

<dataSource name="sqlmaparticles" connectionString="${connectionString}"/>



<transactionManager type="ADO/SWC" />

</database>

    <sqlMaps>

<sqlMap resource="bin/articles.xml" />

</sqlMaps>

</sqlMapConfig>

La balise [properties] désigne le fichier de propriétés dans lequel seront trouvées les valeurs des clés de la forme ${clé} du fichier courant. La balise [provider] indique la méthode d'accès aux données. Chaque méthode est associée à une bibliothèque de classes qui lui est propre. L'attribut [connectionString] de la balise [dataSource] fournit la chaîne identifiant la base de données à exploiter. Enfin la balise <sqlMaps> (au pluriel) sert à définir des fichiers de correspondances [classes .NET <--> tables de SGBD]. Chaque fichier de correspondances est défini par une balise <sqlMap> (au singulier). Ici, nous retrouvons le fichier [articles.xml] déjà présenté.

  • properties.xml

C'est un fichier de propriétés associant des valeurs à des clés.

<?xml version="1.0" encoding="utf-8" ?>

<settings>

<add key="provider" value="OleDb1.1" />

<add

key="connectionString"

value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=dbarticles.mdb;"/>

</settings>

Le fichier ci-dessus donne des valeurs aux deux attributs [provider, connectionString] du fichier [providers.config]. Ci-dessus, le fournisseur d'accès est [OleDb1.1]. Ce fournisseur permet d'accéder aux sources de données disposant d'un pilote OleDB. La chaîne de connexion désigne le fichier ACCESS [dbarticles.mdb] ayant la table [ARTICLES] suivante :

  • providers.config

Ce fichier définit les classes d'accès aux données, classes associées à des fournisseurs d'accès :

<?xml version="1.0" encoding="utf-8" ?>

<providers>

<clear/>

<provider name="Odbc1.1" enabled="true"

assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" connectionClass="System.Data.Odbc.OdbcConnection"

commandClass="System.Data.Odbc.OdbcCommand" parameterClass="System.Data.Odbc.OdbcParameter" parameterDbTypeClass="System.Data.Odbc.OdbcType" parameterDbTypeProperty="OdbcType" dataAdapterClass="System.Data.Odbc.OdbcDataAdapter" commandBuilderClass="System.Data.Odbc.OdbcCommandBuilder" usePositionalParameters = "true"

useParameterPrefixInSql = "false" useParameterPrefixInParameter = "false" parameterPrefix = "@"

/>

<provider name="OleDb1.1" enabled="true"

assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" connectionClass="System.Data.OleDb.OleDbConnection" commandClass="System.Data.OleDb.OleDbCommand"

parameterClass="System.Data.OleDb.OleDbParameter" parameterDbTypeClass="System.Data.OleDb.OleDbType" parameterDbTypeProperty="OleDbType" dataAdapterClass="System.Data.OleDb.OleDbDataAdapter" commandBuilderClass="System.Data.OleDb.OleDbCommandBuilder" usePositionalParameters = "true"

useParameterPrefixInSql = "false" useParameterPrefixInParameter = "false" parameterPrefix = ""

/>

</providers>

Le fichier ci-dessus définit deux fournisseurs d'accès :

  • [ OleDb1.1] pour les sources OleDb
  • [ Odbc1.1] pour les sources Odbc

L'outil Ibatis SqlMap vient avec d'autres définitions de fournisseurs d'accès qui n'ont pas été intégrées au fichier ci-dessus. Au final, la couche [dao] va amener certains fichiers dans les dossiers du service web donnant accès aux couches [domain, dao] de l'application :

Les fichiers amenés par la couche [dao] seront les suivants :

  • [Apache.Avalon.DynamicProxy.dll, articles.xml, IbatisNet.Common.dll, IbatisNet.DataAccess.dll, IbatisNet.DataMapper.dll, log4net.dll, properties.xml, providers.config, sqlmap.config] sont nécessaires à [Ibatis SqlMap].
  • [log4net.dll, Spring.Core.dll] sont nécessaires à Spring.
  • [webarticles-dao.dll] est le code de la couche d'accès à la base de données des articles.
  • [dbarticles.mdb] est la base ACCESS que nous utiliserons pour nos tests.

3.2 La couche [domain]

Le lecteur est invité à relire la définition de la couche [domain] dans l'article 1 et la modification qui lui a été apportée dans l'article

  1. Nous en redonnons les grandes lignes.

3.2.1 Structure de la couche

La couche [domain] contient les éléments suivants :

- [IArticlesDomain]: l'interface d'accès à la couche [domain]

- [Achat] : classe définissant un achat

- [Panier] : classe définissant un panier d'achats

- [AchatsArticles] : classe d'implémentation de l'interface [IArticlesDomain]

3.2.2 L'interface [IArticlesDomain]

L'interface [IArticlesDomain] découple la couche [métier] de la couche [web]. Cette dernière accède à la couche [métier/domain] via cette interface sans se préoccuper de la classe qui l'implémente réellement. L'interface définit les actions suivantes pour l'accès à la couche métier :

Imports Article = istia.st.articles.dao.Article

Namespace istia.st.articles.domain Public Interface IArticlesDomain

' méthodes

Sub acheter(ByVal panier As Panier) Function getAllArticles() As IList

Function getArticleById(ByVal idArticle As Integer) As Article ReadOnly Property erreurs() As ArrayList

End Interface

End Namespace

Function getAllArticles() As IList rend la liste d'objets [Article] de la source de données associée

rend l'objet [Article] identifié par [idArticle]

valide le panier du client en décrémentant les stocks des articles achetés de la quantité achetée - peut échouer si le stock est insuffisant

rend la liste des erreurs qui se sont produites lors de l'achat d'un panier - vide si pas d'erreurs

3.2.3 La classe [Achat]

La classe [Achat] représente un achat du client. Elle a les propriétés et méthodes suivantes :

l'article acheté

la quantité achetée le montant de l'achat

chaîne d'identité de l'objet

New(ByVal unArticle As article, ByVal qte As Integer) le constructeur

3.2.4 La classe [Panier]

La classe [Panier] représente l'ensemble des achats du client. Elle a les propriétés et méthodes suivantes :

la liste des achats du client - liste d'objets de type [Achat] ajoute un achat à la liste des achats enlève l'achat de l'article idAchat

le montant total des achats du panier rend la chaîne d'identité du panier

3.2.5 La classe [AchatsArticles]

L'interface [IArticlesDomain] est implémentée par la classe [AchatsArticles] suivante :

Imports istia.st.articles.dao

Namespace istia.st.articles.domain Public Class AchatsArticles

Implements IArticlesDomain

'champs privés

Private _articlesDao As IArticlesDao Private _erreurs As New ArrayList

' constructeur

Public Sub New(ByVal articlesDao As IArticlesDao)

_articlesDao = articlesDao End Sub

' propriétés

Public ReadOnly Property erreurs() As ArrayList Implements IArticlesDomain.erreurs Get

Return _erreurs End Get

End Property

' méthodes

Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles ' liste de tous les articles

Return _articlesDao.getAllArticles End Function

Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById

' un article particulier

Return _articlesDao.getArticleById(idArticle) End Function

Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter

' achat d'un panier - les stocks des articles achetés doivent être décrémentés

_erreurs.Clear() Dim achat As achat

Dim achats As ArrayList = panier.achats

For i As Integer = achats.Count - 1 To 0 Step -1 ' décrémenter stock article i

achat = CType(achats(i), achat)

If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then ' on n'a pas pu faire l'opération

_erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")

Else

' l'opération s'est faite - on enlève l'achat du panier panier.enlever(achat.article.id)

End If Next

End Sub End Class

End Namespace

Commentaires :

  • cette classe implémente les quatre méthodes de l'interface [IArticlesDomain]. Elle a deux champs privés :

_articlesDao As IArticlesDao l'objet d'accès aux données

_erreurs As ArrayList la liste des erreurs éventuelles. Elle est accessible via la propriété publique [erreurs]

  • pour construire une instance de la classe, il faut fournir l'objet permettant l'accès aux données :

Sub New(ByVal articlesDao As IArticlesDao)

  • les méthodes [getAllArticles] et [getArticleById] s'appuient sur les méthodes de même nom de la couche [dao]
  • la méthode [acheter] valide l'achat d'un panier. Cette validation consiste simplement à décrémenter les stocks des articles achetés. L'achat d'un article n'est possible que si son stock le permet. Si ce n'est pas le cas, l'achat est refusé : il reste dans le panier et une erreur est signalée dans la liste [erreurs]. Un achat validé est retiré du panier et le stock de l'article correspondant décrémenté de la quantité achetée.

3.3 La couche [ui]

Dans l'article 4, l'application WinForms écrite avait la structure suivante :

Utilisateur

Données

VUES

les classes métier, les classes d'accès aux données et la base de données les formulaires Windows

C=contrôleur le moteur [MVC2-win] de traitement des requêtes clientes, les objets [Action] Rappelons les grands principes du fonctionnement de la couche [ui] écrite dans l'article 4:

  1. le contrôleur [BaseControleur] est le coeur de l'application. Toutes les demandes du client transitent par lui. C'est une classe fournie par le moteur [M2VC-win]. On peut dans certains cas être amené à la dériver. Pour les cas simples, ce n'est pas nécessaire.
  2. [BaseControleur] prend les informations dont il a besoin dans un fichier appelé [m2vc-win.exe.config]. Il y trouve la liste des objets [Action] destinés à exécuter les demandes du client, la liste des vues à afficher selon les cas, une liste d'objets [InfosAction] décrivant chaque action. [InfosAction] a les attributs suivants :
  • [vue] : désigne une vue [Form] à afficher si l'action ne consiste qu'à changer de vue.
  • [action] : désigne un objet [Action] à exécuter si l'action demandée nécessite l'exécution d'un code
  • [états] : un dictionnaire associant une vue à chacun des résultats possibles de l'objet [Action]. Le contrôleur affichera la vue associée au résultat renvoyé par l'action.
  1. l'utilisateur a devant lui un formulaire windows. Celui-ci traite certains événements lui-même, ceux qui ne nécessitent pas la couche métier. Les autres sont délégués au contrôleur. On dit alors que la vue demande l'exécution d'une action au contrôleur. Le contrôleur reçoit cette demande sous la forme d'un nom d'action.
  2. [BaseControleur] récupère alors l'instance [InfosAction] liée au nom de l'action qu'on lui demande d'exécuter. Pour cela, il a un dictionnaire associant le nom d'une action à une instance [InfosAction] rassemblant les informations nécessaires à cette action.
  3. si l'attribut [vue] de [InfosAction] est non vide, alors la vue associée est affichée. On passe ensuite à l'étape 9.
  4. si l'attribut [action] de [InfosAction] est non vide, alors l'action est exécutée. Celle-ci fait ce qu'elle a à faire puis rend au contrôleur une chaîne de caractères représentant le résultat auquel elle est parvenue.
  5. le contrôleur utilise le dictionnaire [états] de [InfosAction] pour trouver la vue V à afficher. Il l'affiche.
  6. ici une vue a été affichée. Le contrôleur s'est synchronisé avec et attend que la vue déclenche une nouvelle action. Celle-ci va être déclenchée par une action particulière de l'utilisateur sur la vue, qui à cette occasion va repasser la main au contrôleur en lui donnant le nom de l'action à exécuter.
  7. le contrôleur reprend à l'étape 1

L'utilisation du moteur [M2VC-win] nécessite la présence des fichiers [m2vc-win.dll, m2vc-win.exe, m2vc-win.exe.config, Spring.Core.dll, log4net.dll] dans le dossier des exécutables de la couche [ui] :



3.4 Les vues de l'application web [webarticles-part3]

Les différentes vues présentées à l'utilisateur seront les suivantes :

- la vue "LISTE" qui présente une liste des articles en vente

 - la vue [INFOS] qui donne des informations supplémentaires sur un produit :

- la vue [PANIER] qui donne le contenu du panier du client - la vue [PANIERVIDE] pour le cas où le panier du client est vide

- la vue [ERREURS] qui signale toute erreur d'achat d'articles

3.5 Fonctionnement de l'application web [webarticles-part3]

Nous présentons ci-dessous l'enchaînement des vues lors d'une utilisation typique de l'application :

A partir de la vue ci-dessus, nous utilisons les options du menu pour faire des opérations. En voici quelques unes. La colonne de gauche représente la demande du client et la colonne de droite la réponse qui lui est faite.

4 Les couches d'adaptation de l'application web [webarticles-part3]

Rappelons la structure de l'application [webarticles-part3] que nous voulons construire :

 Nous voulons réutiliser autant que possible les couches [ui, domain, dao] développées dans les articles précédents. L'idéal serait qu'on utilise les DLL de ces couches sans toucher au code source. Il nous faut pour cela créer des couche d'adaptation entre le client et le serveur. L'architecture précédente devient alors la suivante :

Couche interface utilisateur [ui] 2  1 Couche métier [domain] Couche d'accès aux données [dao]

  TCP-IP  

SPRING

  SPRING

Nous ajoutons deux couches notées ci-dessus 1 et 2 :

  • la couche 1 va exposer un service web d'accès à la couche [domain]. Nous l'appellerons couche [webservice].
  • la couche 2 va implémenter les proxy d'accès aux services web de la couche 1. Elle le fait de façon transparente pour la couche [ui] qui ne saura pas que les données qu'elle reçoit et envoie vont sur le réseau. Nous l'appellerons couche [proxy].

5 La couche [webservice] de l'application web [webarticles-part3]

5.1 Architecture de la solution Visual Studio

L'architecture VS de la couche du service web est la suivante :

  • dans [References], on trouve les deux DLL d'implémentation des couches [domain] et [dao] appelées [webarticles-domain.dll] et [webarticles-dao.dll]
  • ces deux DLL sont dans le dossier [bin] du projet. Elles s'appuient elles-mêmes sur les DLL nécessaires aux outils [Ibatis SqlMap] et [Spring] qui elles-aussi sont placées dans le dossier [bin].
  • toujours dans [bin], le fichier [webarticles-service.dll] est la DLL générée par le projet [webservice]. Elle contiendra le service web de [webarticles-part3].
  • dans le dossier [istia/st/bin/articles/webservice], on trouve les codes source de la couche des services web. Les classes et interfaces de celle-ci sont placées dans l'espace de noms [istia.st.articles.webservice].
  • directement dans le dossier du projet [webservice], on trouve les fichiers nécessaires à l'exécution du service web :
  • web.config : le fichier de configuration de l'application web
  • global.asax : la classe d'initialisation de l'application web
  • dbarticles.mdb, articles.xml, sqlmap.config, properties.xml, providers.config : les fichiers nécessaires à la couche [dao] d'accès aux données. Celles-ci sont dans le fichier ACCESS [dbarticles.mdb].
  • webarticles.asmx : le fichier du service web.
  • le projet [webservice] est configuré pour générer la DLL [webarticles-service] :

Nous nous intéressons tout d'abord aux classes et interfaces de la couche [webservice].

5.2 L'interface IArticlesWebService

L'interface [IArticlesWebService] a pour but de définir les méthodes d'accès à la couche des services web. Son code est le suivant :

Imports istia.st.articles.dao Imports istia.st.articles.domain

Namespace istia.st.articles.webservice Public Interface IArticlesWebService

' méthodes utiles

Function getAllArticles() As IList

Function getArticleById(ByVal idArticle As Integer) As Article Function acheterPanier(ByVal panier As Panier) As WSPanier

' méthodes de tests

Function status() As ArrayList Function testAchatPanier() As WSPanier

End Interface

End Namespace

  • la méthode [getAllArticles] fournira la liste des articles
  • la méthode [ getArticleById] fournira l'article identifié par son numéro idArticle
  • la méthode [acheterPanier] est la méthode qui permettra d'acheter un panier
  • les méthodes [status] et [testAchatPanier] sont des méthodes à finalité de tests. Elles seront explicitées plus loin.

Ces méthodes seront toutes exposées en tant que méthodes du service web.

5.3 Un service web sans état

Reprenons l'architecture de l'application [webarticles-part3] :

Couche interface utilisateur [ui] 2  1 Couche métier [domain] Couche d'accès aux données [dao]

  TCP-IP  

  SPRING  

  SPRING

Le service web implémenté dans la couche 1 est un service sans état. Il ne conserve aucune information entre deux demandes successives d'un client. Il n'y a pas la notion de Session comme dans une application web classique. Nous le verrons grâce à la méthode [status] du service web. Cette méthode n'a été implémentée que pour montrer ce point.

Quelles conséquences a pour notre application cette absence d'état ? L'absence d'état a une importance lorsque pour exécuter une opération demandée par l'utilisateur, la couche [proxy] a besoin de faire plusieurs échanges avec la couche [webservice] et que ceux- ci ont besoin d'avoir une mémoire commune gérée par le serveur. L'absence d'état du service web interdit ce mode d'échanges.

Listons les différentes actions d'un utilisateur et voyons comment celles-ci peuvent être traitées :

  • liste des articles

L'utilisateur demande la liste des articles.

La méthode

        Function getAllArticles() As IList

du service web va être interrogée par la couche 2 [proxy]. Elle va rendre une liste d'articles éventuellement vide. Elle peut également lancer une exception si l'accès aux données n'a pu se faire. Les méthodes des services web propagent correctement les exceptions via le réseau. Aussi la couche [proxy] pourra gérer l'éventuelle exception. Il n'y a là besoin que d'un échange.

  • demande d'un article particulier

La méthode

  Function getArticleById(ByVal idArticle As Integer) As Article

du service web va être interrogée par la couche 2 [proxy]. Elle va rendre l'article demandé s'il existe ou une exception s'il n'existe pas ou si l'accès aux données s'est révélé impossible. Il n'y a là besoin que d'un échange.

  • gestion du panier

Autour de la gestion du panier on trouve les actions suivantes :

  1. voir le panier
  2. ajouter un article dans le panier
  3. retirer un article du panier
  4. acheter le panier

Dans les applications [webarticles] classiques précédentes, le panier d'un utilisateur était mémorisé dans la session de celui-ci. Il était ainsi conservé au fil des échanges entre le client et le serveur. Cette solution n'est pas possible avec un service web sans état. Il nous faut trouver un autre moyen pour gérer le panier du client. La solution proposée est la suivante :

  • le panier sera géré par la couche [ui]. C'est donc cette couche qui détiendra l'objet [Panier] de l'utilisateur.
  • les opérations 1 (voir le panier), 2 (ajouter un article) et 3 (retirer un article) ne nécessitent pas l'interrogation du service web. Elles pourront être gérées directement par la couche [ui]
  • l'opération 4 (acheter le panier) est la seule opération sur le panier nécessitant l'interrogation du service web. Rappelons comment la couche [ui] achetait le panier dans les versions précédentes de [webarticles]. Elle utilisait la méthode Sub acheter(ByVal panier As Panier) de l'interface [IArticlesDomain]. Cette méthode ne rend aucun résultat. Le contenu du panier est modifié. Après son achat, il est soit vide, soit il contient les articles qui n'ont pu être achetés pour cause de stocks insuffisants. Dans ce cas, la couche [ui] peut avoir la liste des erreurs en faisant appel à la méthode

  ReadOnly Property erreurs() As ArrayList

de l'interface [IArticlesDomain]. On voit donc que pour exécuter l'achat du panier, la couche [ui] fait deux échanges avec la couche [domain] :

  1. elle demande l'achat du panier
  2. elle demande la liste des erreurs mémorisées par la couche [domain] à l'issue de l'échange précédent.

Avec un service web sans état tout ceci n'est plus possible :

  • à l'issue de l'échange 1, le service web connaît les éventuelles erreurs qui se sont produites mais il n'est pas capable de les mémoriser pour les donner lors de l'échange suivant. Il faut qu'il les donne à l'issue de l'échange 1. La liste des erreurs doit donc être dans la réponse du service web.
  • à l'issue de l'échange 1, la couche [ui] s'attend à avoir un panier vidé des articles achetés. Dans les applications [webarticles] précédentes, les couches [ui] et [domain] travaillaient sur le même panier car la couche [ui] transmettait à la couche [domain] une référence du panier. Ici ce n'est plus le cas. Les échanges client-serveur se font par valeur et non par référence. La couche [ui] ne peut transmettre à la couche [domain] qu'une copie de la valeur du panier. Cette valeur est transmise dans un format XML. Après l'échange 1, le service web sait ce qu'est devenu le contenu du panier. Il doit donc transmettre la nouvelle valeur de celui-ci dans sa réponse. Celle-ci sera également transmise dans un format XML.

Pour tenir compte des remarques précédentes, le service web offre la méthode suivante pour l'achat d'un panier :

  Function acheterPanier(panier As WSPanier) As WSPanier

  • le paramètre d'entrée est le panier à acheter. Il a été envoyé par le client via le réseau.
  • la réponse est de type [WSPanier] défini dans la paragraphe suivant. La réponse contient le nouveau panier après achat ainsi que la liste des éventuelles erreurs d'achats.



476