Web API : exporter une liste de blobs sous la forme de fichiers dans un zip
Un petit poste d’astuce qui pourra vous faire gagner du temps. Se servir de blob est bien pratique lorsque l’on veut stocker des informations (la définition de niveaux d’un jeu par exemple). Pour les récupérer on peut le faire de façon unitaire mais récupérer une archive avec tous les fichiers est quand même plus pratique.
La technique que j’utilise est la suivante :
- Je récupère le container de blobs,
- Je lance les tâches de lecture du contenu des blobs et je les stockent dans un dictionnaire,
- Je créé une archive zip et j’ajoute, blob par blob les différentes entrées,
- Je retourne le contenu dans la réponse HTTP.
La compression ZIP est réalisée à l’aide du package ‘ICSharpCode.SharpZipLib” disponible sur Nuget.
Vous noterez que je n’attends pas séquentiellement la lecture de chaque blob mais que je lance tous les téléchargements d’un coup.
private async Task<HttpResponseMessage> ListAsZip() { try { //Récupération du container var container = await GetBlobContainerAsync().ConfigureAwait(false); var blobs = container.ListBlobs().OfType<CloudBlockBlob>(); //Créations des tasks de lecture des contenus dans un dico var contentsTasks = blobs .ToDictionary( //Clef b => b.Name, //Valeur b => container.GetBlockBlobReference(b.Name).DownloadTextAsync()); //Création d'un flux d'écriture zip var memoryStream = new MemoryStream(); ZipOutputStream zipOutputStream = new ZipOutputStream(memoryStream); zipOutputStream.IsStreamOwner = false; foreach (var kVp in contentsTasks) { //Récupération du contenu du blob string blobContent = await kVp.Value; //Petite vérification d'usage if (string.IsNullOrEmpty(blobContent)) { continue; } var buffer = Encoding.UTF8.GetBytes(blobContent); //Création de l'entrée ZIP dans l'archive ZipEntry entry = new ZipEntry(ZipEntry.CleanName(kVp.Key + ".lvl")); entry.Size = buffer.Length; // On donne la taille d'origine que l'on connait entry.DateTime = DateTime.Now; zipOutputStream.PutNextEntry(entry); //Ecriture complète du contenu zipOutputStream.Write(buffer, 0, buffer.Length); zipOutputStream.Flush(); } // On flushe le tout et on ferme les zipOutputStream.Flush(); zipOutputStream.Close(); zipOutputStream.Dispose(); memoryStream.Seek(0, SeekOrigin.Begin); //Création du content de la réponse en dumpant le flux mémoire var content = new ByteArrayContent(memoryStream.ToArray()); //On assigne les bons headers de type de fichier content.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); //On retourne un fichier content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "levels.zip", }; // On créé la réponse finale var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = content }; return response; } catch (Exception e) { //En cas d'erreur, on retourne le message dans une 404 return Request.CreateResponse(HttpStatusCode.NotFound, e.Message); } }
Je déploye ma web api sur Azure mobile Services et voici quelques erreurs que j’ai rencontrées en chemin :
- Utiliser StreamContent ou PushStreamContent semble me retourne une erreur 402 une fois déployé : j’utilise donc un ByteArrayContent à la place
- Ne pas renseigner la taille du fichier d’origine semble poser problème
- Ne pas vérifier que le nom donné à l’entrée Zip est correct (ZipEntry.CleanName) peut poser problème
- Bien penser à flusher et fermer les flux manipulés.
En espérant que cela vous fera gagner du temps :)
Commentaires