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}");
}
}