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

image

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

Voici le code permettant de générer la table :

Code SQL de génération de la table [Person].
Sélectionnez
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 évidement 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.

image

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.
Sélectionnez
/// <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).
Sélectionnez
/// <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 serialiseur 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).
Sélectionnez
/// <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 serialiseur 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 chaine de caractères au format XML.
Sélectionnez
/// <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.
Sélectionnez
/// <summary>
/// Envoie une chaîne de caractère 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))
    {
        // Ecriture 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.

image

Voici le code source de la page « aspx » :

Code XHTML de la page Default.aspx.
Sélectionnez
<%@ 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.
Sélectionnez
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.
Sélectionnez
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.
Sélectionnez
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.

image

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.

image

Sauvegardez-le. Le code XML contient ceci :

Contenu du fichier XML vide.
Sélectionnez
<?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.

image

Confirmez la boîte de dialogue suivante.

image

Le tableau s'affiche directement ainsi :

image

Remplissez les deux lignes.

image

Ne sauvegardez pas le fichier, il faut l'exporter. Faites un clic droit sur le tableau, Menu XML > Exporter.

image

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

image

Une fois le processus terminé nous pouvons constater que la table n'est plus vide.

image

Il reste à cliquer sur le bouton 3 pour obtenir le fichier XML rempli des données de la base.

image

À la différence du premier, les éléments ont une valeur.

Contenu du fichier XML rempli avec les données de la base.
Sélectionnez
<?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.
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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.
Sélectionnez
public static class Tools
{
    /// <summary>
    /// Envoie une chaîne de caractère au client sous la forme d'un fichier Xml
    /// </summary>
    /// <param name="s">La chaîne de caractère 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))
        {
            // Ecriture 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.
Sélectionnez
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 pour avoir corrigé les fautes d'orthographe et de grammaire C cool