begin process at 2010 03 22 00:48:42
  Trouver un code source :
 
dans
 
Accueil > 

Tutoriels

 > 

Web Services

 > DATA TRANSFER OBJECT PATTERN, DATA ACCESS & WEB SERVICE

DATA TRANSFER OBJECT PATTERN, DATA ACCESS & WEB SERVICE


 Information sur le tutoriel

Note :
Aucune note


 Description

Ce tutorial expose un cas d'utilisation du design Pattern "Data Transfer Object". Ce dernier s'intègre dans une solution .Net permettant l'accès aux données via les Web Services.

Tutorial

Présentation du besoin

Pour les différents exemples, nous établissons le besoin suivant:

Accès au différentes fonctions d’interaction avec la table « Person » : Ajout, Modification, Suppression et Récupération.


Table « Person »

Colonnes

Type

Description

ID

int

Primary Key, Not Null

Name

varchar(50)

Null

Age

int

Not Null

Tableau : Table "Person"

Analyse du besoin

Bien entendu, un besoin tel que se révèle assez simple : nous pourrions tout à fait coder directement les quatre méthodes.

Néanmoins, comme tout bon développeur que nous sommes, nous souhaitons quand même rendre la chose la plus évolutive et générique possible. C’est dans ce cadre que nous pouvons présenter l’architecture suivante :


Figure : Architecture Web Services basé sur le design pattern DTO

A ce niveau là, vous allez me dire : « il est bien beau le schéma mais ça sert à quoi ». Je vous propose donc de poursuivre la suite du document qui va expliquer module par module le rôle de chacun.

Développement

Conseil :    créer une solution contenant un projet distinct pour chaque composant.

(les références projet sont exprimées par les double flèches du schéma ci-dessus)

Utilisation du « Data Transfer Object » Pattern

Une utilisation de ce patron (couplé au design pattern : « Assembler ») décompose ce besoin en terme d’objets métiers sous 3 parties distinctes. Tout ceci dans le but de rendre plus simple l’utilisation des objets métiers correspondant aux besoins fonctionnels.

Les « Domain Objects »

Il s’agit là d’un simple flux contenant des données génériques. Il existe nativement dans le framework .Net sous la forme de classes telles que : DataSet, DataReader…

Pour notre exemple, la classe utilisée sera « DataSet ».

Les « Data Transfer Objects »

Présentée par le "Data Transfer Object" pattern (url: http://msdn.microsoft.com/en-us/library/ms978717.aspx ), une classe DTO est un conteneur métier.

Chaque classe respecte les spécifications fonctionnelles tant au niveau de ses champs que des propriétés, mais n'implémente aucune logique métier au niveau des méthodes. En effet, il ne s'agit là que d'un conteneur permettant le transport des informations métier.

Nous ajouterons à chaque classe DTO la sérialisation XML qui permettra, à terme, la communication avec n'importe quelle plate-forme et n'importe quel langage.


Code :

Fichier de classe « Person.cs » :

[XmlRoot(ElementName="Personne")]

public class Person

{

private int _id = -1;

private string _name = null;

private int _age = -1;


[XmlAttribute(AttributeName="Identifiant")]

public int Id { get { return _id; } set { _id=value;} }

[XmlAttribute(AttributeName="Nom")]

public string Name { get { return _name; } set { _name=value;} }

[XmlAttribute(AttributeName="Age")]

public int Age { get { return _age; } set { _age=value;} }


public Person()

{

}


public Person(int id, string name, int age)

{

_id = id;

_name = name;

_age = age;

}

}


Fichier de classe « PersonList.cs »

public class PersonsList : List<Persons>

{

public PersonsList()

: base()

{

}


}

La classe Assembler

Cette classe constitue le lien entre les DTO et les DO. Elle permet, via des méthodes statiques, la conversion d’un DTO vers un DO et inversement.

Dans ce cadre là, nous pourrons facilement fournir un flux XML contenant des objets DTO. A l’inverse, nous pourrons interpréter facilement un flux XML en DTO.


Code :

Fichier de classe statique « Assembler » :

public static class Assembler

{

public static DataSet CreatePersonDataSet(PersonList listPerson)

{

DataSet rDataset = new DataSet("Person");

DataTable table = rDataset.Tables.Add();


table.Columns.Add("id", typeof(int));

table.Columns.Add("name", typeof(string));

table.Columns.Add("age", typeof(int));


foreach(Person curPerson in listPerson)

{

DataRow curRow = table.NewRow();

curRow[0] = (int) curPerson.Id;

curRow[1] = (string) curPeron.Name;

curRow[2] = (int) curPerson.Age;


table.Rows.Add(curRow);

}


return rDataSet;

}


public static PersonList CreatePersonDTO(DataSet ds)

{

PersonList rList = new PersonList();


foreach(DataRow curRow in ds.Tables[0].Rows)

{

rList.Add(new Person(

(int) curRow["id"],

(string) curRow["name"],

(int) curRow["age"]);

}


return rList;

}

}

« Business Data Access Objects » & « Data Access Objects »

Ces deux composants, normalement distincts, sont exposés ici comme un seul et unique composant. Celui-ci s’occupe de toutes les fonctionnalités concernant l’accès aux données : récupération des personnes, ajout / modification / suppression d’une personne.


Code :

Fichier « DataAccess.cs »

public class DataAccess

{

private string _connectionString = null;


public string ConnectionString

{ get { return _connectionString; } }


public DataAccess(string connectionString)

{

_connectionString = connectionString;

}


public void AddPerson(int id, string name, int age)

{

using (SqlConnection connection = new SqlConnection(_connectionString))

{

connection.Open();

//-- INSERT --

using (SqlCommand command = new SqlCommand("INSERT INTO Person VALUES(@id, @name, @age)", connection))

{

command.Parameters.Add(new SqlParameter("@id", id));

command.Parameters.Add(new SqlParameter("@name", name));

command.Parameters.Add(new SqlParameter("@age", age));

command.ExecuteNonQuery();

}

connection.Close();

}

}


public void UpdatePerson(int id, string name, int age)

{

using (SqlConnection connection = new SqlConnection(_connectionString))

{

connection.Open();

//-- UPDATE --

using (SqlCommand command = new SqlCommand("UPDATE Person SET name=@name, age=@age WHERE id=@id", connection))

{

command.Parameters.Add(new SqlParameter("@name", name));

command.Parameters.Add(new SqlParameter("@age", age));

command.Parameters.Add(new SqlParameter("@id", id));

command.ExecuteNonQuery();

}

connection.Close();

}

}


public void DeletePerson(int id)

{

using (SqlConnection connection = new SqlConnection(_connectionString))

{

connection.Open();

//-- DELETE --

using (SqlCommand command = new SqlCommand("DELETE Person WHERE id=@id", connection))

{

command.Parameters.Add(new SqlParameter("@name", name));

command.Parameters.Add(new SqlParameter("@age", age));

command.Parameters.Add(new SqlParameter("@id", id));

command.ExecuteNonQuery();

}

connection.Close();

}

}


public DataSet GetPersons()

{

DataSet ds = new DataSet("Person");

using (SqlConnection connection = new SqlConnection(_connectionString))

{

connection.Open();

//-- SELECT --

using(SqlDataAdapter da = new SqlDataAdapter( "SELECT id, name, age FROM Person", connection))

{

da.Fill(ds);

}

connection.Close();

}

return ds;

}

}


Comme vous l’aurez compris, l’idéal consisterait à séparer la partie métier de l’accès aux données. L’accès aux données ne s’occuperait que la connexion et l’exécution des requêtes sur la base. Ainsi, nous pourrions facilement changer d’un type de base de données à un autre (ex : Sql Server, Oracle, Base Access, MySQL…) sans affecter la logique métier d’accès aux données.

Pour ce faire, je ne pourrais que vous conseiller d’utiliser une librairie très connue des développeurs .Net : « Enterprise Library 3.1 ». Celle-ci intègre un composant nommé « Data Access Application Block » qui correspond tout à fait à ce besoin. Il permet la sélection du type de la base de données via le fichier de configuration.

Url : http://msdn.microsoft.com/en-us/library/aa480453.aspx

Les « Web Services »

Il ne nous reste donc plus qu’à donner l’accès aux données aux utilisateurs. Une couche Web Service se révèle idéale pour la situation. Celle-ci fera interface entre utilisateur et exécution des demandes.


Code :

Fichier « Persons.asmx »

public class WebService : System.Web.Services.WebService

{

public WebService() { }


[WebMethod]

public void AddPerson(int id, string name, int age)

{

DataAccess da = new DataAccess("connection");

da.AddPerson(id, name, age);

}


[WebMethod]

public void UpdatePerson(int id, string name, int age)

{

DataAccess da = new DataAccess("connection");

da.UpdatePerson(id, name, age);

}


[WebMethod]

public void DeletePerson(int id)

{

DataAccess da = new DataAccess("connection");

da.DeletePerson(id, name, age);

}


[WebMethod]

public string GetPersons()

{

DataAccess da = new DataAccess("connection");

DataSet ds = da.GetPersons();


//Create DTO

PersonList personList = Assembler.CreatePersonDTO(ds);


//Serialize to XML

StringWriter sw = new StringWriter();

XmlSerializer s = new XmlSerializer(typeof(personList));

s.Serialize(sw, personList);

return sw.ToString();

//--

}

}

Synthèse

Alors voila, la mise en place est terminée.

Mais, quels sont donc les avantages d’une telle architecture ?

Un tel projet devient alors une façade de services multi plateforme et multi langage. N’importe quel type de projet (application Windows, site web, service Windows…) pourra se greffer, interagir et interpréter très facilement (normalisation XML) les données, et ce, quelque soit le langage utilisé.

L’ajout d’une colonne dans la table consistera simplement à ajouter un membre à la classe DTO et deux lignes dans la classe Assembler.

Le changement de type de base de données n’interfèrera qu’avec la partie accès aux données (et en cas d’utilisation de la librairie « Enterprise Library », elle se révélera dans l’unique modification du fichier de configuration).

 Historique

31 juillet 2008 14:06:13 :
Mise à jour du tutorial suite aux questions fréquemment posées. Le but de cette mise à jour est d'être plus explicite pour la communauté.
31 juillet 2008 16:15:13 :
Mise en forme du tutorial
31 juillet 2008 16:41:14 :
Colorisation du code source et quelques corrections...
31 juillet 2008 16:43:44 :
Correction du code source (2)
31 juillet 2008 16:45:05 :
Correction du code source
01 août 2008 09:30:44 :
Correction orthographe
24 septembre 2008 15:54:52 :
Modification suite à une mauvaise interprétation du "Data Transfer Object Pattern". Il ne comporte que la définition d'une classe DTO.
24 septembre 2008 16:02:19 :
Correction
30 janvier 2009 11:12:13 :
Orthographe
24 septembre 2009 11:46:20 :
Relecture...
24 septembre 2009 11:50:54 :
Relecture...
24 septembre 2009 11:51:56 :
Relecture...
24 septembre 2009 12:07:22 :
Relecture...
24 septembre 2009 12:14:18 :
Relecture...

Commentaires

Commentaire de remsrock le 07/07/2008 17:48:47

Re !

Dans la méthode public DataSet GetPersons(), visual studio me dit "not all code paths return a value", je te traduis pas, c'est évident...
Que faire ?

ensuite, c'est normal que public class PersonList : List<Person> { /* ... */ } ne contient rien ? (désolé chui débutant, mes questions peuvent être stupides :) )

Enfin, ne serait il pas mieux que les méthodes add update et delete retourne un booléen pour savoir si l'action s'est bien déroulé ? (en servant de command.ExecuteNonQuery() qui si il retourne 0 -> false, sinon true)

Commentaire de remsrock le 07/07/2008 18:18:31

un autre problème :

dans le webservice, les méthodes sont des void, mais y'a des return.
donc il est pas très content.... !

Commentaire de billou_13 le 07/07/2008 18:54:53

Re ^^

1) Concernant la méthode GetPersons(), je ne vois pas pourquoi cette erreur est soulevée. Le "return sw.ToString();" est bien executé en fin de fonction à chaque fois. A moins que tu aies tout mis dans un try{}catch{}.

2) Concernant "public class PersonList : List<Person> { /* ... */ }", il faut bien entendu que tu implémentes l'intérieur du commentaire. C'est à toi de voir ce que tu veux mettre. A la limite au moins un constructeur.

3) Pour les méthodes Add, Delete, etc... tu peux tout à fait mettre un booléen qui sera renvoyé par ta webmethod. A toi de voir ^^.
A savoir: si une exception est soulevée pendant un traitement, elle sera bien renvoyée par ta webmethod (sous forme d'une SoapException), donc c'est ok pour les eceptions.

4) Pour les méthodes qui sont en void avec des returns, c'est une erreur de ma part. Je n'ai pas réussi à mettre à jour la source de mon tuto ^^. Tu as tout à fait raison.


Bon courage et n'hésites pas à revenir vers moi pour quoi que ce soit.

Commentaire de remsrock le 08/07/2008 09:36:50

Hello l'ami !

- mais du coup, les void avec return je fais quoi ? je sors juste les return ?

- pour la liste, j'ai fais ça mais ça me semble trop simple... lol. ca doit resembler à ça ?

public class PersonsList : List<Persons>
{
    private List<Persons> _PersonsList;

    public WarningsList()
    {
        this._PersonsList = new List<Persons>();
    }

    public List<Persons> getPersongsList()
    {
        return this._PersonsList;
    }
}

- enfin, pour le GetPersons, je viens de voir qu'il n'y a pas de return. c'est ça ? si oui, que faire ? c'est sur ce point que j'ai toujours un peu de mal : quoi faire pour l'affichage xml ?

merci d'avance !

Commentaire de remsrock le 08/07/2008 09:39:11

public PersonsList // me suis trompé ! j'ai mis mon cas perso à la place (WarningsList)

on peut pas éditer ses messages ici !

Commentaire de billou_13 le 08/07/2008 10:11:53

Salut,

Alors:

1) Pour le void avec les return:
  - soit tu laisses void et tu enlève le return
  - soit tu retournes un booléen avec les fonctions Add, Remove, etc... (comme tu m'avais dit) et là, tu retourne un booléen. (change le void en bool)


2) Pour la liste, voici le code commenté:
public class PersonsList : List<Persons>
{
    private List<Persons> _PersonsList;// FAUX CAR TA CLASSE EST DEJA UNE LISTE, PAS BESOIN D'EN AJOUTER UNE AUTRE

    public PersonsList()
      : base()
    {
      //base() appelle le constructeur de la classe qui est dérivée (List<Persons>)
    }

    /* PAS BESOIN DE CETTE METHODE
    public List<Persons> getPersongsList()
    {
        return this._PersonsList;
    }*/
}
Pour d'autres méthodes, à toi d'ajouter si besoin est. Mais cela devrait te suffire.


3) Pour le GetPersons, je suis désolé, j'avais regardé la webmethod et pas la data acess method. Tu as effectivement raison, il manque un return (désolé). Voici le nouveau code:
public DataSet GetPersons()
{
  DataSet ds = new DataSet("Person");
  using (SqlConnection connection = new SqlConnection(_connectionString))
  {
    connection.Open();

    using(SqlDataAdapter da = new SqlDataAdapter( "SELECT id, name, age FROM Person", connection))
    {
      da.Fill(ds);
    }
    connection.Close();
  }

  //Tu ajoute le return ici
  return ds;
}



Voila, désolé pour mes erreurs et bon courage à toi,


Billou_13

Commentaire de remsrock le 08/07/2008 10:41:32

merci pour les aides !

donc, les add, del et update marchent, mais pas le point qui m'intéresse : le getList !
le problème c'est que je n'ai pas d'erreur, j'ai juste "la page web ne peut pas etre affiché" un truc du genre quoi...

(tu me dis si tu veux que je poste ailleurs, car je commence à prendre de la place !)

je fais donc des breakpoint dans le web service

WarningsList warningList = ass.CreateWarningsDTO(ds); // ici !

//Serialize to XML
StringWriter sw = new StringWriter(); // et ici !

sur le premier break voici les valeurs :

da = {DataAccess}
ds = {System.Data.DataSet}
ass = {Assembler}
warningList = null // normal ? ou c'est parceque c'est pile le niveau du breakpoint
puis sw et s = null, mais c'est normal

c'est à partir du second breakpoint que ça plante.

je sens que ça va etre difficile que tu m'aides...

Commentaire de remsrock le 08/07/2008 10:48:02

aussi, c'est normal que dans l'assembler :

public DataSet CreatePersonDataSet(PersonList listPerson)

ne sert jamais ?

Commentaire de billou_13 le 08/07/2008 11:08:08

Effectivement, il va falloir que tu y ailles pas-à-pas avec le debugger afin de voir ce qui cloche.
Les questions à te poser:
  - Est-ce que ton DataSet est bien rempli ?
  - Est-ce que la méthode de la classe Assembler marche bien ?
  - ...

Pour la méthode "public DataSet CreatePersonDataSet(PersonList listPerson)", elle est effectivement pas utilisée dans mon exemple. Mais elle garde son intérêt si plus tard, tu souhaites mettre une méthode dans la classe d'accès aux données qui permet de mettre à jour la table Person avec un DataSet Person (du même type que celui retourné par le GetPersons()).
Par exemple:
1) tu récupère la liste des persons
2) Tu l'a met en DTO via la sérialisation XML
3) Tu bind ta liste sur un GridView
4) L'utilisateur met à jour la liste des personnes (ajout, suppression, modification ...)
5) Il sauvegarde la nouvelle liste.
6) A ce niveau, tu renvoie la liste des personnes à une webmethod en XML sérialisé.
7) Tu utilises l'assembler pour obtenir le DataSet persons
8) Tu appelles la méthode de ta classe d'accès aux données pour mettre à jour la table
Voici un exemple d'utilisation.

Tu peux donc garder cette méthode en vue d'autres développements.

Voila, bon courage pour le debug.


Billou_13

Commentaire de remsrock le 08/07/2008 11:48:10

ok ! c'est good !
pour info, c'était une erreur de récupération de données dans la base de données. il bloquait sur un champ. vive le debug !

donc en fait l' "affichage" de la liste se fait dans un string où il y a toutes les balises. c'est moins jolie qu'afficher directement un doc xml, mais ça on s'en fiche, ce qui est important de savoir, c'est est ce que je pourrais aussi facilement manipuler ce string qu'un doc xml.

sinon, merci encore !

Commentaire de billou_13 le 08/07/2008 11:59:51

Tout à fait, ce n'est qu'une histoire de mise en forme (ou plutôt d'indentation) du XML ^^).
Après l'appel à ta webmethod qui te renvoie la liste des personnes, il te suffit alors de faire l'opération inverser de la sérialisation: la désérialisation.
Voici le code:

//A ajouter en tête de fichier
using System.Xml.Serialization;
using System.IO;

XmlSerializer ser = new XmlSerializer(typeof(PersonList));
StringReader reader = new StringReader(xml);//xml est ton string retourné par la webmethod

// Deserialize xml to the type of DTO
PersonList persons = (PersonList)ser.Deserialize(reader);

Commentaire de remsrock le 08/07/2008 12:19:47

j'ai oublié de noter le tuto !
donc, exactement ce que je cherchais. quelques erreurs on dirait, mais M. Billou est très présent pour répondre aux questions. merci !

Commentaire de billou_13 le 08/07/2008 13:22:41

Merci à toi pour la correction.
Il est vrai que j'avais vu certaines d'entre elles mais je n'arrive pas à mettre à jour le tuto.

Très bonne continuation dans les développements,

Billou_13

Commentaire de padawanette le 30/07/2008 13:26:19

Bonjour, moi aussi, j'ai un peit probleme de fichier XML et de database.

Je n'ai pas vriament compris la structure du projet :  OU doit-on utiliser et coller "Le Data Transfer Object pattern" dans le project du web service ?
C'est quoi cette classe ?

Pourriez vous m'eclairer un peu svp !!

Commentaire de billou_13 le 31/07/2008 10:25:58

Bonjour padawanette,

Saches que j'ai bien pris en compte ta requête et que pour faire plus simple, je suis en train de mettre à jour tout le tutorial. J'espère le livrer le plus tôt possible.
Je te tiendrais au courant.

Bonne journée,


Billou_13

Commentaire de billou_13 le 31/07/2008 14:11:40

Bonjour,

J'ai donc effectuer la mise à jour et j'espère que le tutorial sera des plus explicite.
La mise en forme n'est pas encore parfaite (document word 2007) et je la retoucherai dès que possible.
Mais le fond y est.

@PADAWANETTE: j'espère que cela t'aidera mieux, si ce n'est pas le cas, tiens moi au courant.

Faites moi aussi part de vos remarques, je suis ouvert à toute considération ^^

Bon après midi,


Billou_13

Commentaire de bamb0u le 02/12/2008 12:01:35

Petite question :

Lorsque l'on récupère les données d'une classe sous format xml d'un webservice sans connaitre la structure de cette classe.

Comment peut-on récupérer ses informations dans une classe identique a celle du webservice. Doit-on récréer une nouvelle classe a l'identique ou alors existe-t-il un autre moyen ?

Merci

Commentaire de billou_13 le 02/12/2008 14:24:10

Bonjour,

Si tu as bien séparé toutes les parties exposées dans ce tutoriel au sein de projets différents, il te suffit alors de référencer uniquement le projet contenant les classes DTO.
Ainsi, comme j'expliquais dans les posts précédents, au retour de ta Web method, tu obtiens le xml que tu n'as plus qu'à désérialiser.
Exemple:
//A ajouter en tête de fichier
using System.Xml.Serialization;
using System.IO;

XmlSerializer ser = new XmlSerializer(typeof(PersonList));
StringReader reader = new StringReader(xml);//xml est ton string retourné par la webmethod

// Deserialize xml to the type of DTO
PersonList persons = (PersonList)ser.Deserialize(reader);

Tu auras alors accès à tes objets (DTO) et donc à tes informations. Tu pourras alors faire toutes les actions nécessaires sur ton objet (ajout d'une personne, mise à jour, suppression). Ainsi, tu pourras retourner à une nouvelle web method ton objet sérialiser.


Pour info et personnellement: dans mes projets de ce type, je créé une classe de base qui sera hérité par toutes mes classes DTO.
Exemple: DataTransferObjectBase<T> (où T représente le type de la classe héritée).
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

public class DataTransferObjectBase<T>
{
   /// <summary>
   /// Serializes object into XML
   /// </summary>
   /// <returns>Xml formatted</returns>
   public string Serialize()
   {
      //Serialize to XML
      StringWriter sw = new StringWriter();
      XmlSerializer ser = new XmlSerializer(typeof(T));
      ser.Serialize(sw, this);

      return sw.ToString();
   }

   /// <summary>
   /// Deserializes XML into object
   /// </summary>
   /// <param name="xml">Xml formatted</param>
   /// <returns>T object</returns>
   public static T Deserialize(string xml)
   {
      XmlSerializer ser = new XmlSerializer(typeof(T));
      StringReader reader = new StringReader(xml);

      return (T)ser.Deserialize(reader);
   }
}

Ainsi, tu peux appeler directement ta méthode statique Deserialize qui te retourne ton objet DTO. Et, dans tout tes objets DTO, tu aura l'implémentation de ta méthode Serialize. Ceci permet d'éviter de faire les 3-4 lignes qui permettent la serialization.



Cependant, si le projet qui référencie les web services n'est pas dans le langage .Net, alors il te faudra re-coder tes objets DTO.


Bonne journée,


Billou_13

 Ajouter un commentaire




Nos sponsors


Sondage...

Comparez les prix

CalendriCode

Mars 2010
LMMJVSD
1234567
891011121314
15161718192021
22232425262728
293031    

Consulter la suite du CalendriCode

 
Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel (EBArtSoft), Merci à Vincent pour ses précieux conseils.
CodeS-SourceS.com© Toute reproduction même partielle est interdite sauf accord écrit du Webmaster
CodeS-SourceS.com© est une marque déposée tous droits réservés

Google Coop CodeS-SourceS Google Coop CodeS-SourceS
Temps d'éxécution de la page : 0,328 sec (3)

Nous contacter | Annoncer sur CodeS-SourceS | Mentions légales