Générer et télécharger des fichiers dynamiques via ASP.NET Core Web API

L'implémentation d'une fonctionnalité de téléchargement de fichiers dans une API ASP.NET Core nécessite de répondre à plusieurs problématiques : la génération dynaimque du contenu, la gestion de la mémoire et la compatibilité avec les navigateurs. Cet article détaille comment utiliser FileStreamResult pour retourner des fichiers générés à la volée, comme des exports CSV.

Principes fondamentaux

Dans l'écosystème ASP.NET Core, la classe de base FileResult simplifie l'envoi de fichiers. Pour des données générées dynamiquement sans stockage physique sur le disque, on utilise généralement une combinaison de MemoryStream et de FileStreamResult.

  • MemoryStream : Idéal pour stocker temporairement des données en mémoire vive avant l'envoi.
  • FileStreamResult : Une implémentation de ActionResult qui écrit un flux de données directement dans la réponse HTTP.

Implémentation de la logique d'exportation

Imaginons un scénario où nous devons exporter une liste d'objets au format CSV. La méthode ci-dessous illustre la création du flux de données. Notez l'importance de la gestion du curseur du flux.

public async Task<Stream> GenererExportCsv<T>(IEnumerable<T> donnees)
{
    var fluxMemoire = new MemoryStream();
    // On ne ferme pas le flux ici avec "using" car le framework doit y accéder plus tard
    var redacteur = new StreamWriter(fluxMemoire, System.Text.Encoding.UTF8);

    var proprietes = typeof(T).GetProperties();

    // Écriture de l'en-tête
    var entetes = string.Join(",", proprietes.Select(p => p.Name));
    await redacteur.WriteLineAsync(entetes);

    // Écriture des lignes
    foreach (var item in donnees)
    {
        var valeurs = proprietes.Select(p => p.GetValue(item)?.ToString() ?? string.Empty);
        await redacteur.WriteLineAsync(string.Join(",", valeurs));
    }

    // Crucial : vider le buffer pour s'assurer que tout est écrit dans le MemoryStream
    await redacteur.FlushAsync();

    // Crucial : réinitialiser la position au début du flux avant la lecture par le contrôleur
    fluxMemoire.Position = 0;

    return fluxMemoire;
}

Configuration du contrôleur API

Le contrôleur doit appeler la logique de génération et configurer les en-têtes HTTP appropriés, notamment le type MIME et le nom du fichier suggéré pour le navigateur.

[HttpGet("telecharger/csv")]
[ProducesResponseType(typeof(FileStreamResult), 200)]
public async Task<IActionResult> ExporterDonnees()
{
    var serviceExport = new ExportService();
    var listeProduits = await _db.Produits.ToListAsync();
    
    var streamResultat = await serviceExport.GenererExportCsv(listeProduits);
    
    // Définition du type MIME pour le format CSV
    string typeMime = "text/csv";
    string nomFichier = $"Export_{DateTime.Now:yyyyMMdd}.csv";

    return new FileStreamResult(streamResultat, typeMime)
    {
        FileDownloadName = nomFichier
    };
}

Points techniques critiques

1. La gestion du cycle de vie du flux

Une erreur fréquente consiste à encapsuler le MemoryStream dans un bloc using à l'intérieur de la méthode de génération. Cela ferme le flux avant que l'infrastructure ASP.NET Core ne puisse le lire pour envoyer la réponse. ASP.NET Core se charge automatiquement de libérer les ressources des flux retournés via FileStreamResult.

2. La position du curseur

Après l'écriture des données par le StreamWriter, le curseur se trouve à la fin du flux. Si vous tentez de retourner le flux tel quel, le fichier téléchargé sera vide (0 octet). Il est impératif de repositionner le curseur à zéro via stream.Position = 0;.

3. Vidage des tampons (Flush)

Le StreamWriter utilise un tampon interne. Sans un appel explicite à Flush() ou FlushAsync(), les dernières lignes de données pourraient ne pas être transférées vers le MemoryStream sous-jacent.

4. Types MIME universels

Si le format de fichier est inconnu ou binaire, le type MIME générique application/octet-stream doit être privilégié pour forcer le téléchargement par le navigateur.

Étiquettes: ASP.NET Core Web API FileStreamResult MemoryStream C#

Publié le 17 juillet à 16h15