Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum. Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

DATA TRANSFER OBJECT PATTERN, DATA ACCESS & WEB SERVICE


Information sur le tutorial

Catégorie :Web Services Tutorial .NET ( DotNet ) Date de création : 12/11/2007 18:45:59 Vu : 6 372 fois

Note :
Aucune note

Commentaire sur cette source (18)
Ajouter un commentaire et/ou une 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

1. Présentation du besoin

Pour les différents exemples, nous établissons le besoin suivant:
Accès aux 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

2. 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 :

 Architecture_WS.png

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.

3. 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)

3.1 Utilisation du « Data Transfer Object » Pattern

Une utilisation de ce patron (couplé au 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.

3.1.1 Les « Domain Objects »

Il s'agit là d'un simple flux contenant les données. Il existe nativement dans le framework .Net sous la forme de classes telles que : DataSet, DataReader.
Ces objets sont complètement génériques dans le sens où ils peuvent accueillir n'importe quelle structure de classe.
Pour notre exemple, la classe utilisée sera « DataSet » car cette dernière peut être directement retournée par l'exécution d'une requête SQL.

3.1.2 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 totalement métier. Chaque classe respecte les contraintes fonctionnelles du besoin.
La seule contrainte que nous poserons est la sérialisation XML de cette classe. En effet, ce type de sérialisation 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( int id, string name, int age)
  {
    _id = id;
    _name = name;
    _age = age;
  }
}

Fichier de classe « PersonList.cs »

public

class PersonList : List < Person >
{
  public PersonList()
    :
base ()
  {
  }
}

3.1.3 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 )curPerson.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;
  }
}
 

3.2 « 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 ( "@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

3.3 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);
  }

  [
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();
    //--
  }
}
 

4. 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 le changement de configuration).
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
signaler à un administrateur
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)

signaler à un administrateur
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.... !

signaler à un administrateur
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.

signaler à un administrateur
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 !

signaler à un administrateur
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 !

signaler à un administrateur
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

signaler à un administrateur
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...

signaler à un administrateur
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 ?

signaler à un administrateur
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

signaler à un administrateur
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 !

signaler à un administrateur
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);

signaler à un administrateur
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 !

signaler à un administrateur
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

signaler à un administrateur
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 !!

signaler à un administrateur
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

signaler à un administrateur
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

signaler à un administrateur
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

signaler à un administrateur
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...

CalendriCode

Juillet 2009
LMMJVSD
  12345
6789101112
13141516171819
20212223242526
2728293031  

Consulter la suite du CalendriCode

Comparez les prix Nouvelle version

Photothèque Nouveau !



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
Temps d'éxécution de la page : 0,109 sec

Google Coop CodeS-SourceS Google Coop CodeS-SourceS


Certaines images présentes sur le site (notament certains avatars) sont issues des collections IconShock, donc si vous souhaitez utiliser ces icons vous devez les acheter, ne les copiez pas et ne utilisez pas dans vos sites et applications sans les avoir commandé.