Depuis que je fais des applications métier, il y a une tâche qu'on me demande régulièrement : exporter des données dans Excel. Pour faire ça, il y a plusieurs solutions, plus ou moins simples, chacune avec avantages ou inconvénients.
On peux par exemple : utiliser des outils externes (Telerik, SSRS), faire de l'interrop Excel, ou générer un simple fichier texte au format CSV qui sera compris sans trop de problème avec le logiciel.

J'ai décidé de mon coté de me lancer dans la solution qui n'est pas la plus simple, mais qui me semble la plus propre : générer directement le fichier xlsx à l'aide du Open XML SDK ; avec l'avantage de pouvoir obtenir directement un fichier natif sans s'encombrer de dépendances, mais un développement plus compliqué car on va devoir travailler directement sur la structure du fichier OpenXML. Le SDK est compatible avec le framework .NET depuis la version 3.5, et avec .NET Standard 1.3, donc il devrait fonctionner dans à peu près tous vos projets C#.

Ici on fait un simple export de données, ça reste relativement simple puisqu'il va nous suffir de remplir des cases ; ça sera autre chose si on veut utiliser des fonctionnalités plus avancées d'Excel. Pour mon code, je me suis basé sur ce post de blog, qui explique comment démarrer : Creating a simple XLSX from scratch using the Open XML SDK.

Génération du fichier Excel

On commence donc par installer le Open XML SDK, qui se trouve maintenant sur Nuget : DocumentFormat.OpenXml, puis on va créer notre fichier :

using (var package = SpreadsheetDocument.Create(@"C:\\Temp\\Export.xlsx", SpreadsheetDocumentType.Workbook))
{
    // Initialisation du document
    var workbookPart = package.AddWorkbookPart();

    var workbook = new Workbook();
    workbook.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
    workbookPart.Workbook = workbook;

    var sheets = new Sheets();
    workbook.Append(sheets);

    // Ajout du premier onglet
    var sheet = new Sheet() { Name = "Onglet1", SheetId = 1, Id = "rId1" };
    sheets.Append(sheet);
    var worksheetPart = workbookPart.AddNewPart<WorksheetPart>("rId1");
    var worksheet = new Worksheet();
    var sheetData = new SheetData();
    worksheet.Append(sheetData);
    worksheetPart.Worksheet = worksheet;
}

Ce code là est le stric minimum pour avoir un document qui s'ouvre dans Excel sans erreur : on initialise le document, on crée une liste d'onglets, on ajoute un premier onglet et on le prépare à recevoir des données.

Ensuite, on va pouvoir remplir les cases :

    // Première ligne
    var row = new Row();
    sheetData.Append(row);

    // Ajout d'une cellule à la ligne
    Cell cell = new Cell()
    {
        CellReference = "A1",
        DataType = CellValues.InlineString
    };
    InlineString inlineString = new InlineString();
    Text text = new Text();
    text.Text = "Bonjour Excel !";
    inlineString.Append(text);
    cell.Append(inlineString);
    row.Append(cell);

Et voilà, la première case est remplie. Il vous suffit de faire ça pour chaque case et c'est bon !
Le format fonctione par ligne (Row), donc tant que vous êtes sur la même ligne (A1, B1, C1) vous ajoutez dans le même row, et dès que vous allez à la ligne vous en créez un nouveau.

Mutualisation du code

Maintenant que ça c'est fait, je me suis dit que c'était pas la peine de le refaire à chaque nouveau projet. J'ai donc créé un package Nuget qui permets de faire ça plus facilement. Il suffit de lui envoyer une liste d'objets ou le résultat d'une requête SQL, et ça génère le fichier.

Le package Nuget est ici, et le code source est là. Ça permets d'exporter au format CSV ou XLSX. D'ailleurs au passage, j'en profite pour demander de l'aide, si quelqu'un sait comment générer des fichiers OpenDocument pour LibreOffice, ça m'intéresse !

Pour générer vos fichier, vous pouvez ajouter des attributs ExportColumn sur vos classes :

public class SampleData
{
    [ExportColumn(Title = "Number", Order = 1)]
    public int IntData { get; set; }

    [ExportColumn(Title = "Text", Order = 2)]
    public string TextData { get; set; }
}

Puis les envoyer dans le DataExporter :

var data = new List<SampleData>()
{
    new SampleData{ IntData=5, TextData="Hello"},
    new SampleData{ IntData=20, TextData="Yoo"},
    new SampleData{ IntData=10, TextData="This is some text"},
};

var xlsxExporter = new XlsxDataExporter();
var xlsxResult = xlsxExporter.Export(data);

Si vous voulez juste exporter le résultat d'une requête SQL sans le mapper, vous pouvez aussi :

using (SqlConnection connection = new SqlConnection(connectionString))
{
    SqlCommand command = new SqlCommand("SELECT OrderID, CustomerID FROM dbo.Orders", connection);
    connection.Open();
    SqlDataReader reader = command.ExecuteReader();
    var xlsxExporter = new XlsxDataExporter();
    var xlsxResult = xlsxExporter.Export(reader);
}

Le résultat est un MemoryStream, que vous pouvez enregistrer directement dans un fichier, ou retourner comme résultat d'une action dans un site MVC.

J'espère que tout ça vous sera utile. J'ai écrit l'API pour mon propre usage, n'hésitez pas à tester et me faire des retours car ça peut encore évoluer.