I. Présentation▲
Il est parfois nécessaire de permettre aux utilisateurs d'une application d'importer ou d'exporter des données en masse. Les moyens offerts pour une saisie facile ne sont pas très nombreux. Il en est un qui s'offre comme une évidence : Excel.
Dans la famille des tableurs, Excel 2007 est sans doute la meilleure solution. Excel 2007 permet de saisir plus d'un million de lignes et il permet de travailler avec des fichiers XML de manière très conviviale.
Ce tutoriel va présenter une méthode d'import et d'export de données au format XML grâce à la sérialisation.
II. Qu'est-ce que la Sérialisation/Désérialisation XML ?▲
Il est possible de lire dans un classeur Excel comme dans une table d'une base de données. Cette méthode est assez performante à condition que le fichier soit vraiment bien formaté. C'est un peu risqué, car il faut faire beaucoup de vérifications sur le nom des feuilles de calcul, la position des données dans la feuille et leur type.
Pour renforcer la qualité de l'application, il est préférable d'utiliser un fichier XML. Grâce au procédé de désérialisation, ce fichier sera formaté de telle sorte qu'il sera validé et interprété automatiquement par quelques lignes de code.
Sur la MSDN, on peut lire cette définition :
« La sérialisation XML est le processus qui consiste à convertir des propriétés et champs publics d'un objet dans un format série (dans ce cas, XML) à des fins de stockage et de transport. La désérialisation recrée l'objet dans son état d'origine à partir du résultat XML ».
Ce procédé est très utilisé, en particulier pour les services Web .Net. (Créer et Consommer un service Web avec .NET par Stéphane EyskensStéphane Eyskens). Vous pouvez aussi consulter ces articles : La sérialisation XML avec .NETLa sérialisation XML avec .NET et Aller plus loin avec la sérialisation XMLAller plus loin avec la sérialisation XML rédigés par Thomas LevesqueThomas Levesque.
Le programme que nous allons réaliser va nous permettre de transformer les lignes d'une table de notre base de données en une liste d'objets et cette liste en un fichier XML exploitable par Excel (export). Il nous permettra aussi de réaliser l'opération inverse (import).
III. La base de données▲
Elle se compose d'une seule table [Persons] de trois champs :
- [PersonId] : identifiant de l'enregistrement ;
- [LastName] : le nom de famille ;
- [FirstName] : le prénom.
Voici le code permettant de générer la table :
USE
[Excel]
GO
/****** Object: Table [dbo].[Persons] Script Date: 10/02/2010 15:25:51 ******/
SET
ANSI_NULLS ON
GO
SET
QUOTED_IDENTIFIER
ON
GO
SET
ANSI_PADDING ON
GO
CREATE
TABLE
[dbo]
.[Persons]
(
[PersonId]
[uniqueidentifier]
NOT
NULL
CONSTRAINT
[DF_Persons_PersonId]
DEFAULT
(
newid())
,
[LastName]
[varchar]
(
20
)
NOT
NULL
,
[FirstName]
[varchar]
(
20
)
NOT
NULL
)
ON
[PRIMARY]
GO
SET
ANSI_PADDING OFF
GO
IV. Solution▲
J'utiliserai Visual Studio 2010 et ciblerai le Framework 4. Nous ferons une solution avec trois projets : données/modèle, métier, interface utilisateur. LINQ sera évidemment de la partie.
IV-A. Projet d'accès aux données et modèle : la DAL▲
Pour ce tutoriel, nous irons un peu vite et utiliserons directement les objets mis à disposition par le contexte de données.
Je rappelle que la création d'une classe dédiée à la fourniture de données peut être très utile pour ajouter des requêtes compilées.
Ajoutez à la solution un projet librairie de classes. Ajoutez un composant LINQ to SQL et nommez-le « Person.dbml ». Ajoutez les tables précédemment créées.
Vous pouvez prendre exemple sur le code de cet article pour faire des requêtes optimisées : Internationalisation d'une Application ASP.Net grâce à une base de données.
IV-B. Projet logique métier : Business▲
Ajoutez à la solution un projet librairie de classes. Ajoutez une classe nommée « PersonManager.cs ».
Référencez le projet « Data ».
Voici tout d'abord la méthode qui nous permettra d'insérer ou de mettre à jour un enregistrement. Elle admet une liste d'objets « Person ». Pour chacun des éléments de la liste, si l'enregistrement existe déjà, on le met à jour sinon, on l'insère.
///
<
summary
>
/// Insère un objet Person en base ou met à jour un enregistrement existant
///
<
/summary
>
///
<
param
name
=
"persons"
>
Liste d'objet Person
<
/param
>
public
static
void
InsertOrUpdate
(
IEnumerable<
Person>
persons)
{
using
(
PersonDataContext db =
new
PersonDataContext
(
))
{
foreach
(
var
p in
persons)
{
// Si le Guid est vide
if
(
p.
PersonId ==
Guid.
Empty)
{
// On génère un nouvel identifiant
p.
PersonId =
Guid.
NewGuid
(
);
// On insère l'enregistrement
db.
Persons.
InsertOnSubmit
(
p);
}
// Sinon
else
{
// Vérifions que l'enregistrement n'existe pas déjà
Person person =
db.
Persons.
Where
(
x =>
x.
PersonId ==
p.
PersonId).
FirstOrDefault
(
);
// Si il existe
if
(
person !=
null
)
{
// On met à jour les propriétés
person.
LastName =
p.
LastName;
person.
FirstName =
p.
FirstName;
}
else
{
// On insère un nouvel enregistrement
db.
Persons.
InsertOnSubmit
(
p);
}
person =
null
;
}
// On soumet les modifications
db.
SubmitChanges
(
);
}
}
}
Voici, ci-dessous, la méthode permettant de transformer une liste d'objets « Person » en une chaîne de caractères au format XML :
///
<
summary
>
/// Cette méthode accepte une énumération la sérialise en un flux Xml et
/// envoie ce flux dans la réponse au client pour lui permettre de le sauvegarder.
///
<
/summary
>
///
<
param
name
=
"persons"
>
L'énumération d'objets
<
/param
>
///
<
param
name
=
"page"
><
/param
>
public
static
string
SerializeMe
(
List<
Person>
persons)
{
// Creation du sérialiseur qui va transformer la liste d'objets en Xml
XmlSerializer serializer =
new
XmlSerializer
(
typeof
(
List<
Person>
));
// Ouverture de l'espace mémoire
using
(
MemoryStream mem =
new
MemoryStream
(
))
{
// Création du XML correspondant à la liste d'objets par la sérialisation
serializer.
Serialize
(
mem,
persons);
// Return the Xml string
return
Encoding.
UTF8.
GetString
(
mem.
ToArray
(
));
}
}
L'opération inverse se réalise ainsi :
///
<
summary
>
/// Cette méthode accepte un flux et tente de le désérialiser en une
/// liste d'objets.
///
<
/summary
>
///
<
param
name
=
"buffer"
>
La flux
<
/param
>
public
static
List<
Person>
DeserializeMe
(
byte
[]
buffer)
{
// Creation du sérialiseur qui va transformer la liste d'objets en Xml
XmlSerializer serializer =
new
XmlSerializer
(
typeof
(
List<
Person>
));
// Ouverture de l'espace mémoire
using
(
MemoryStream mem =
new
MemoryStream
(
buffer))
{
XmlTextReader reader =
new
XmlTextReader
(
mem);
// Création du XML correspondant à la liste d'objets par la sérialisation
if
(
serializer.
CanDeserialize
(
reader))
{
return
serializer.
Deserialize
(
reader) as
List<
Person>;
}
else
{
return
null
;
}
}
}
Une petite surcharge en passant au cas où on aurait besoin d'envoyer une chaîne de caractères à désérialiser.
///
<
summary
>
/// Cette méthode accepte une chaîne et tente de la désérialiser
/// en une liste d'objets.
///
<
/summary
>
///
<
param
name
=
"str"
><
/param
>
///
<
returns
><
/returns
>
public
static
List<
Person>
DeserializeMe
(
string
str)
{
return
DeserializeMe
(
Encoding.
UTF8.
GetBytes
(
str));
}
Enfin, voici la méthode qui nous permettra d'envoyer le fichier XML au client. Cette méthode admet une référence vers la page en paramètre.
///
<
summary
>
/// Envoie une chaîne de caractères au client sous la forme d'un fichier Xml
///
<
/summary
>
///
<
param
name
=
"s"
>
La chaîne de caractères au format Xml
<
/param
>
///
<
param
name
=
"page"
>
La page dans laquelle inscrire la réponse
<
/param
>
public
static
void
Download
(
string
s,
Page page)
{
// Supprime tout le contenu du flux de sortie envoyé au client
page.
Response.
Clear
(
);
// Ajout d'un en-tête décrivant le flux envoyé au client
page.
Response.
AddHeader
(
"content-disposition"
,
"attachment; filename=Persons.xml"
);
page.
Response.
ContentType =
"text/xml"
;
// Création d'un flux d'écriture "sw" pour envoyer le Xml dans la réponse de la page "Response.OutputStream"
using
(
StreamWriter sw =
new
StreamWriter
(
page.
Response.
OutputStream,
Encoding.
UTF8))
{
// Écriture du contenu de la mémoire "mem" dans le flux "sw"
sw.
Write
(
s);
}
// Fin de la réponse. Le code après n'est pas exécuté
page.
Response.
End
(
);
}
IV-C. Projet interface utilisateur : WebApplication▲
Afin de faire fonctionner tout cela, nous allons avoir besoin d'un bouton pour recevoir le modèle du fichier, d'un contrôle pour sélectionner un fichier, d'un bouton pour l'envoyer au serveur et enfin, d'un bouton pour récupérer le fichier XML avec les données de la base. La page devrait avoir cette apparence.
Voici le code source de la page « aspx » :
<%@ Page Language
=
"C#"
AutoEventWireup
=
"true"
CodeBehind
=
"Default.aspx.cs"
Inherits
=
"WebApplication.Default"
%>
<!
DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<html xmlns
=
"http://www.w3.org/1999/xhtml"
>
<head runat
=
"server"
>
<title></title>
</head>
<body>
<form id
=
"form1"
runat
=
"server"
>
<blockquote>
<fieldset>
<legend>Recevoir</legend><span>
Obtenir le fichier Xml vide</span>
<asp:
Button
ID
=
"Button1"
runat
=
"server"
Text
=
"Bouton 1"
OnClick
=
"Button1_Click"
/><br />
</fieldset>
</blockquote>
<blockquote>
<fieldset>
<legend>Envoyer</legend>
<asp:
FileUpload ID
=
"FileUpload1"
runat
=
"server"
/><br />
<span>Envoyer le fichier Xml vers le serveur</span>
<asp:
Button ID
=
"Button2"
runat
=
"server"
Text
=
"Bouton 2"
OnClick
=
"Button2_Click"
/>
</fieldset>
</blockquote>
<blockquote>
<fieldset>
<span>Obtenir le fichier Xml avec les données de la table</span>
<asp:
Button
ID
=
"Button3"
runat
=
"server"
Text
=
"Bouton 3"
OnClick
=
"Button3_Click"
/>
</fieldset>
</blockquote>
</form>
</body>
</html>
Lors du clic sur le bouton 1, nous allons constituer une liste factice pour fournir un fichier XML avec une structure exploitable.
Pour une raison qui m'est inconnue, il faut au moins deux éléments dans la liste pour qu'Excel les affiche correctement.
protected
void
Button1_Click
(
object
sender,
EventArgs e)
{
// Constitution d'une liste de trois objets Person
List<
Person>
persons =
new
List<
Person>(
);
persons.
Add
(
new
Person
(
) {
PersonId =
Guid.
Empty,
LastName =
string
.
Empty,
FirstName =
string
.
Empty }
);
persons.
Add
(
new
Person
(
) {
PersonId =
Guid.
Empty,
LastName =
string
.
Empty,
FirstName =
string
.
Empty }
);
// Envoi du fichier au client
PersonManager.
Download
(
// Serialisation de la liste
PersonManager.
SerializeMe
(
persons),
// Passage de la page pour y écrire le flux
this
.
Page
);
}
Lors du clic sur le bouton 2, le code va envoyer le flux binaire du fichier pour qu'il soit désérialisé. La liste obtenue sera envoyée pour que les éléments soient insérés ou mis à jour.
protected
void
Button2_Click
(
object
sender,
EventArgs e)
{
// Si le fichier envoyé est bien un fichier Xml
if
(
FileUpload1.
HasFile &&
FileUpload1.
PostedFile.
ContentType ==
"text/xml"
)
{
PersonManager.
InsertOrUpdate
(
// Déserialisation du flux
PersonManager.
DeserializeMe
(
// Passage du fichier sous forme de tableau de bytes
FileUpload1.
FileBytes
)
);
}
}
Enfin, le bouton 3 permettra d'envoyer une liste au client, constituée à partir des données en base.
protected
void
Button3_Click
(
object
sender,
EventArgs e)
{
List<
Person>
persons =
PersonManager.
Load
(
);
// Envoi du fichier au client
PersonManager.
Download
(
// Serialisation de la liste
PersonManager.
SerializeMe
(
persons),
// Passage de la page pour y écrire le flux
this
.
Page
);
}
V. Mise en pratique▲
Constatez tout d'abord que votre table est vide.
Lancez l'application et cliquez sur le bouton 1. La page vous propose de télécharger un fichier pour l'ouvrir ou le sauvegarder. Ce fichier sert de modèle.
Sauvegardez-le. Le code XML contient ceci :
<?xml version="1.0"?>
<ArrayOfPerson
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
:
xsd
=
"http://www.w3.org/2001/XMLSchema"
>
<Person>
<PersonId>
00000000-0000-0000-0000-000000000000</PersonId>
<LastName />
<FirstName />
</Person>
<Person>
<PersonId>
00000000-0000-0000-0000-000000000000</PersonId>
<LastName />
<FirstName />
</Person>
</ArrayOfPerson>
Si vous n'assignez pas les propriétés « nullables », elles n'apparaîtront pas dans le XML.
Ouvrez le fichier avec Excel. Ce dernier devrait vous proposer de l'ouvrir en tant que « Tableau XML ». Acceptez.
Confirmez la boîte de dialogue suivante.
Le tableau s'affiche directement ainsi :
Remplissez les deux lignes.
Ne sauvegardez pas le fichier, il faut l'exporter. Faites un clic droit sur le tableau, Menu XML > Exporter.
Exportez le fichier au format XML en écrasant la version que vous venez de télécharger.
Retournez sur l'application Web, sélectionnez votre fichier grâce au contrôle « FileUpload ». Cliquez sur le bouton 2.
Le fichier est téléchargé et désérialisé. Un point d'arrêt nous permet de constater que nos objets sont bien là.
Une fois le processus terminé nous pouvons constater que la table n'est plus vide.
Il reste à cliquer sur le bouton 3 pour obtenir le fichier XML rempli des données de la base.
À la différence du premier, les éléments ont une valeur.
<?xml version="1.0"?>
<ArrayOfPerson
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
:
xsd
=
"http://www.w3.org/2001/XMLSchema"
>
<Person>
<PersonId>
ad55a71d-f204-466a-ab54-1dd53c409e27</PersonId>
<LastName>
VERSAIRE</LastName>
<FirstName>
Annie</FirstName>
</Person>
<Person>
<PersonId>
73f61362-ffbe-4fb0-aac0-aaea373a8c8f</PersonId>
<LastName>
TERIEUR</LastName>
<FirstName>
Alex</FirstName>
</Person>
</ArrayOfPerson>
VI. Pour aller plus loin▲
VI-A. Utilisation de la généricité pour éviter la duplication du code▲
Peut-être qu'en regardant ce code et notamment les méthodes de sérialisation vous vous êtes dit que tout cela n'est pas très optimisé. Vous avez raison. En l'état, il faut adapter les méthodes de sérialisation et désérialisation pour chaque type d'objets. Ce serait très long et très improductif.
Pour résoudre ce petit inconvénient, une toute petite modification suffira. Nous allons rendre ces méthodes génériques. Il faut tout d'abord créer une nouvelle classe que nous nommerons « GenericSerializer ». Voici son code :
public
static
class
GenericSerializer<
T>
{
}
Notez bien le « T ». Cela va nous permettre de préciser dynamiquement le type utilisé par la classe.
Ajoutons la méthode de sérialisation :
public
static
string
SerializeMe
(
List<
T>
list)
{
XmlSerializer serializer =
new
XmlSerializer
(
typeof
(
List<
T>
));
using
(
MemoryStream mem =
new
MemoryStream
(
))
{
serializer.
Serialize
(
mem,
list);
return
Encoding.
UTF8.
GetString
(
mem.
ToArray
(
));
}
}
Remarquez une nouvelle fois l'utilisation du « T ».
N'importe quelle lettre fera l'affaire. La lettre « T » est pratique, car elle correspond à l'initiale de « Type ».
Et enfin la méthode de désérialisation.
public
static
List<
T>
DeserializeMe
(
byte
[]
buffer)
{
XmlSerializer serializer =
new
XmlSerializer
(
typeof
(
List<
T>
));
using
(
MemoryStream mem =
new
MemoryStream
(
buffer))
{
XmlTextReader reader =
new
XmlTextReader
(
mem);
if
(
serializer.
CanDeserialize
(
reader))
{
return
serializer.
Deserialize
(
reader) as
List<
T>;
}
else
{
return
null
;
}
}
}
Voici un exemple d'utilisation lors de l'insertion :
public
static
void
InsertOrUpdate
(
IEnumerable<
Person>
persons)
{
string
str =
GenericSerializer<
Person>.
SerializeMe
(
persons as
List<
Person>
);
/*
*
* Reste du code
*
*/
}
VI-B. Utilisation d'une boîte à outils▲
La méthode « Download » permettant l'envoi d'un fichier au client n'est pas propre à la classe « PersonManager ». Celle-ci est dédiée à la gestion du type « Person ». Il faut donc déplacer la méthode « Download » dans une nouvelle classe. Nous l'appellerons « Tools ». Voici son code :
public
static
class
Tools
{
///
<
summary
>
/// Envoie une chaîne de caractères au client sous la forme d'un fichier Xml
///
<
/summary
>
///
<
param
name
=
"s"
>
La chaîne de caractères au format Xml
<
/param
>
///
<
param
name
=
"page"
>
La page dans laquelle inscrire la réponse
<
/param
>
public
static
void
Download
(
string
s,
Page page)
{
// Supprime tout le contenu du flux de sortie envoyé au client
page.
Response.
Clear
(
);
// Ajout d'un en-tête décrivant le flux envoyé au client
page.
Response.
AddHeader
(
"content-disposition"
,
"attachment; filename=Persons.xml"
);
page.
Response.
ContentType =
"text/xml"
;
// Création d'un flux d'écriture "sw" pour envoyer le Xml dans la réponse de la page "Response.OutputStream"
using
(
StreamWriter sw =
new
StreamWriter
(
page.
Response.
OutputStream,
Encoding.
UTF8))
{
// Écriture du contenu de la mémoire "mem" dans le flux "sw"
sw.
Write
(
s);
}
// Fin de la réponse. Le code après n'est pas exécuté
page.
Response.
End
(
);
}
}
Il suffira de l'utiliser ainsi :
protected
void
Button3_Click
(
object
sender,
EventArgs e)
{
List<
Person>
persons =
PersonManager.
Load
(
);
// Envoi du fichier au client
Tools.
Download
(
// Serialisation de la liste
GenericSerializer<
Person>.
SerializeMe
(
persons),
// Passage de la page pour y écrire le flux
this
.
Page
);
}
VII. Conclusion▲
Vous voici maintenant bien outillé pour faire vos exports et imports dans votre base de données.
Remerciements▲
Merci à pixelomilcouleurspixelomilcouleurs et jacques_jeanjacques_jean d'avoir corrigé les fautes d'orthographe et de grammaire.