Importer et exporter des données au travers d'une application ASP.Net

Ou comment se servir de la sérialisation XML et d'Excel pour ses mises à jour en masse.

Immobilis

Dans ce tutoriel, nous allons traiter d'une méthode d'importation et d'exportation de données grâce à Excel 2007. Commentez ♪♪

- Titre : Importer et exporter des données au travers d'une application ASP.Net

- Auteur : Immobilis

- Parution : 27 octobre 2010

- Mise à jour : 27 octobre 2010

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2010 Julien. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- I. Présentation

- II. Qu'est-ce que la Sérialisation/Désérialisation XML ?

- III. La base de données

- IV. Solution

- IV-A. Projet d'accès aux données et modèle : la DAL

- IV-B. Projet logique métier : Business

- IV-C. Projet interface utilisateur : WebApplication

- V. Mise en pratique

- VI. Pour aller plus loin

- VI-A. Utilisation de la généricité pour éviter la duplication du code

- VI-B. Utilisation d'une boîte à outils

- VII. Conclusion

- Remerciements

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 Eyskens). Vous pouvez aussi consulter ces articles : La sérialisation XML avec .NET et Aller plus loin avec la sérialisation XML rédigés par Thomas 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 :

Code SQL de génération de la table [Person].

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.

Méthode permettant l'insertion ou la mise à jour des enregistrements.

/// <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 :

Méthode permettant la sérialisation (transformation en 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 :

Méthode permettant la désérialisation (transformation en liste d'objets).

/// <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.

Surcharge de la désérialisation admettant une chaîne de caractères au format XML.

/// <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.

Méthode permettant l'envoi du fichier XML au client.

/// <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 » :

Code XHTML de la page Default.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>&nbsp;<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>&nbsp;
<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>&nbsp;<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.

Code behind du bouton n°1.

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.

Code behind du bouton n° 2.

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.

Code behind du bouton n° 3.

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 :

Contenu du fichier XML vide.

<?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.

Contenu du fichier XML rempli avec les données de la base.

<?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 :

La classe GenericSerializer.

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 :

Code de la méthode SerializeMe

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.

Code de la méthode DeserializeMe

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 :

Exemple d'utilisation lors de l'appel de la méthode InsertOrUpdate

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 :

Code de la boîte à outils.

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 :

Utilisation de la boîte à outils.

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 à pixelomilcouleurs et jacques_jean d'avoir corrigé les fautes d'orthographe et de grammaire.