Conversion de collections de données en fichiers CSV avec C# et réflexion

Contexte et exigences

L'objectif est de concevoir un mécanisme générique capable d'exporter une collection de données (List<T>) vers un fichier au format CSV. Le type T peut être n'importe quelle classe métier. Le système doit permettre de sélectionner un sous-ensemble de propriétés à exporter et de définir des en-têtes de colonnes personnalisés pour chaque propriété.

Conception de la solution

Pour répondre à ces exigences de manière flexible, l'approche suivante est adoptée :

  • Utilisation de la réflexion (reflection) pour lire dynamiquement les valeurs des propriétés spécifiées.
  • Mise en place d'un dictionnaier de correspondance pour mapper les noms de propriétés C# avec les en-têtes de colonnes CSV.
  • Génération d'une chaîne de caractères formatée selon les standards CSV (gestion des séparatuers et des échappements de caractères).
  • Séparation stricte des responsabilités : la génération du contenu texte est isolée de la logique d'écriture sur le système de fichiers.

Implémentation de l'utilitaire CSV

La classe utilitaire ci-dessous prend en charge la transformation de la collection en texte CSV. Elle inclut une gestion basique des échappements pour les valeurs contenant des virgules, des guillemets ou des retours à la ligne.

public static class CsvExporter
{
    public static string BuildCsvContent<T>(IEnumerable<T> data, Dictionary<string, string> columnMappings)
    {
        var stringBuilder = new StringBuilder();
        
        // Génération des en-têtes
        stringBuilder.AppendLine(string.Join(",", columnMappings.Keys));
        
        // Extraction des propriétés par réflexion
        var properties = typeof(T).GetProperties();
        
        foreach (var item in data)
        {
            var rowValues = new List<string>();
            foreach (var propName in columnMappings.Values)
            {
                var prop = properties.FirstOrDefault(p => p.Name == propName);
                if (prop != null)
                {
                    var value = prop.GetValue(item)?.ToString() ?? string.Empty;
                    
                    // Échappement CSV standard
                    if (value.Contains(",") || value.Contains("\"") || value.Contains("\n"))
                    {
                        value = $"\"{value.Replace("\"", "\"\"")}\"";
                    }
                    rowValues.Add(value);
                }
                else
                {
                    rowValues.Add(string.Empty);
                }
            }
            stringBuilder.AppendLine(string.Join(",", rowValues));
        }
        
        return stringBuilder.ToString();
    }
}

Exemple d'utilisation

Prenons l'exemple d'une classe représentant des employés :

public class Employee
{
    public string FirstName { get; set; }
    public string Department { get; set; }
    public int Age { get; set; }
}

Voici comment appeler l'utilitaire pour générer le contenu texte en mappant uniquement le prénom et le département :

public string ExportStaffToCsv(List<Employee> staff)
{
    var mappings = new Dictionary<string, string>
    {
        { "Prénom", "FirstName" },
        { "Service", "Department" }
    };

    return CsvExporter.BuildCsvContent(staff, mappings);
}

Écriture sur le disque et compatibilité Excel

La couche supérieure est responsable de l'interaction avec l'utilisateur pour choisir l'emplacement de sauvegarde et de l'écriture effective du fichier. Il est crucial d'utiliser l'encodage UTF-8 avec BOM (Byte Order Mark) pour garantir que les caractères spéciaux et les accents soient correctement interprétés par Excel lors de l'ouverture du fichier CSV.

private bool PromptSaveLocation(out string selectedPath)
{
    var dialog = new SaveFileDialog
    {
        FileName = $"export_{DateTime.Now:yyyyMMdd_HHmmss}",
        DefaultExt = ".csv",
        Filter = "Fichiers CSV (*.csv)|*.csv"
    };

    if (dialog.ShowDialog() == true)
    {
        selectedPath = dialog.FileName;
        return true;
    }

    selectedPath = string.Empty;
    return false;
}

private void WriteCsvToDisk(string path, string csvContent)
{
    try
    {
        // UTF-8 avec BOM pour une compatibilité optimale avec Excel
        var utf8BomEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
        
        using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write))
        using (var writer = new StreamWriter(stream, utf8BomEncoding))
        {
            writer.Write(csvContent);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Erreur lors de l'écriture du fichier : {ex.Message}");
    }
}

Étiquettes: C# csv Reflection StreamWriter Generic Types

Publié le 18 juin à 17h16