Aller au contenu principal

30-4 Contrôleur d'API pour recevoir un fichier

Pour recevoir des fichiers et les stocker, nous allons créer un nouveau contrôleur dont le rôle sera précisément cela: recevoir un fichier et le stocker dans le dossier Storage.

Créer le contrôleur UploadsController

  1. Sous le projet Snowfall.Web.Api, sous le dossier Controllers -> Add -> Scaffolded Item
  2. Choisir API Controller - Empty
  3. Nommer le contrôleur UploadsController
Snowfall.Web.Api/Controllers/UploadsController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Snowfall.Web.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UploadsController : ControllerBase
{
}
}

Injecter IWebHostEnvironment et IConfiguration

Pour avoir accès au dossier courant, nous aurons besoin de IWebHostEnvironement et pour accéder à appsettings.json qui contient les détails sur le dossier de stockage il nous faudra IConfiguration.

Injectons ces derniers via le constructeur du nouveau contrôleur:

Snowfall.Web.Api/Controllers/UploadsController.cs
public class UploadsController : ControllerBase
{
private readonly IWebHostEnvironment _environment;
private readonly IConfiguration _configuration;

public UploadsController(IWebHostEnvironment environment,
IConfiguration configuration)
{
_environment = environment;
_configuration = configuration;
}
}

Créer l'action Create via POST

Les fichiers seront reçus via une requête HTTP POST, avec en argument un fichier.

Créons la coquille de cette fonction:

Snowfall.Web.Api/Controllers/UploadsController.cs
[HttpPost]
public async Task<IActionResult> Create([FromForm] IFormFile file)
{
}
info

[FromForm] est un attribut utilisé pour indiquer que la valeur du paramètre doit être obtenue à partir des données de formulaire envoyées par le client lors de la requête HTTP. Cela est couramment utilisé lors de la soumission de formulaires avec des données de type multipart/form-data pour les téléversements de fichiers, par exemple.

Lorsque vous utilisez [FromForm] dans un contrôleur API, il indique au cadriciel de rechercher la valeur du paramètre dans les données du formulaire plutôt que dans d'autres sources potentielles comme l'URI de la requête, les données JSON du corps de la requête ou les en-têtes HTTP.

Créer un DTO ResultatUpload

Le contrôleur retournera de l'information sur le fichier. Pour contenir cette information, on utilisera un DTO.

Il sera nécessaire de retourner au client les informations suivantes:

  • Si le téléversement a été un succès
  • Nom du fichier
  • URL du fichier
  • Erreur, si pertinent.
  1. Sous le projet Snowfall.Application, sous le dossier Dtos -> Add -> Directory.
  2. Nommez le dossier Uploads
  3. Sous le nouveau dossier Uploads -> Add -> Class/Interface
  4. Nommez la classe ResultatUpload
Snowfall.Application/Dtos/Uploads/ResultatUpload.cs
public enum ErreurUpload
{
TailleMax,
FichierVide,
Ecriture,
Lecture
}

public class ResultatUpload
{
public bool EstSucces { get; set; }
public string? NomFichier { get; set; }
public string? UrlFichier { get; set; }
public ErreurUpload? Erreur { get; set; }
}
info

Pour représenter les différents codes d'erreur, on peut utiliser un enum. Cela est flexible et descriptif et permet de gérer facilement plusieurs cas de figure.

L'utilisation d'un enum a du sens dans le cadre d'un projet où les même DTO sont partagés par le client et le serveur. En effet, au besoin, le client a accès à l'enum de son côté et saura qu'une valeur de "0" correspond à TailleMax.

Créer la base de la fonction Create

On commence par créer quelques variables de base dont nous aurons besoin pour traiter le fichier reçu.

Snowfall.Web.Api/Controllers/UploadsController.cs
[HttpPost]
public async Task<IActionResult> Create([FromForm] IFormFile file)
{
// Nom de fichier source tel que reçu du client
string nomFichierSource = file.FileName;

// La taille maximale de fichier acceptée (5 Mo)
long tailleFichierMax = 1024 * 1024 * 5; // 5mb

// L'URL de base qui sera utilisée pour retourner l'URL du fichier
var baseResourceUrl = new Uri($"{Request.Scheme}://{Request.Host}");
}

De plus, on peut initialiser le DTO de résultat de téléversement avec la variable EstSucces à false. true sera assigné à cette variable lorsque le téléversement sera complété, à condition qu'aucune erreur n'ait été rencontrée.

Snowfall.Web.Api/Controllers/UploadsController.cs
public async Task<IActionResult> Create([FromForm] IFormFile file)
{
// Nom de fichier source tel que reçu du client
string nomFichierSource = file.FileName;

// La taille maximale de fichier acceptée (5 Mo)
long tailleFichierMax = 1024 * 1024 * 5; // 5mb

// L'URL de base qui sera utilisée pour retourner l'URL du fichier
var baseResourceUrl = new Uri($"{Request.Scheme}://{Request.Host}");

var resultatUpload = new ResultatUpload()
{
EstSucces = false,
};
}

Ensuite, une vérification est effectuée afin de s'assurer que la taille du fichier correspond aux attentes. Si ce n'est pas le cas, on retourne BadRequest.

Snowfall.Web.Api/Controllers/UploadsController.cs
public async Task<IActionResult> Create([FromForm] IFormFile file)
{
// Nom de fichier source tel que reçu du client
string nomFichierSource = file.FileName;

// La taille maximale de fichier acceptée (5 Mo)
long tailleFichierMax = 1024 * 1024 * 5; // 5mb

// L'URL de base qui sera utilisée pour retourner l'URL du fichier
var baseResourceUrl = new Uri($"{Request.Scheme}://{Request.Host}");

var resultatUpload = new ResultatUpload()
{
EstSucces = false,
};

if (file.Length == 0)
resultatUpload.Erreur = ErreurUpload.FichierVide;
else if (file.Length > tailleFichierMax)
resultatUpload.Erreur = ErreurUpload.TailleMax;

if(resultatUpload.Erreur is not null)
return BadRequest(resultatUpload);
}

Procéder à la sauvegarde du fichier

On peut finalement sauvegarder le fichier sur le disque!

Pour écrire un fichier sur le disque, la classe FileStream peut être utilisée.

Vous remarquerez que les fichiers reçus sont renommés à l'aide d'un GUID.

info

Les GUID (Globally Unique Identifiers) sont des identifiants uniques, générés aléatoirement, qui peuvent être utilisés pour nommer les fichiers stockés. Ils présentent plusieurs avantages par rapport aux noms de fichiers traditionnels basés sur des noms d'utilisateur, des dates ou des séquences numériques.

Le plus important, dans notre cas, est que les GUID garantissent l'unicité des noms de fichiers, ce qui évite les conflits et les écrasements involontaires de fichiers existants. De plus ils rendent les noms de fichiers impossibles à deviner, ce qui dans certain cas peut assurer une sorte de sécurité à ces derniers (si pertinent).

Snowfall.Web.Api/Controllers/UploadsController.cs
public async Task<IActionResult> Create([FromForm] IFormFile file)
{
// Nom de fichier source tel que reçu du client
string nomFichierSource = file.FileName;

// La taille maximale de fichier acceptée (5 Mo)
long tailleFichierMax = 1024 * 1024 * 5; // 5mb

// L'URL de base qui sera utilisée pour retourner l'URL du fichier
var baseResourceUrl = new Uri($"{Request.Scheme}://{Request.Host}");

var resultatUpload = new ResultatUpload()
{
EstSucces = false,
};

if (file.Length == 0)
resultatUpload.Erreur = ErreurUpload.FichierVide;
else if (file.Length > tailleFichierMax)
resultatUpload.Erreur = ErreurUpload.TailleMax;

if(resultatUpload.Erreur is not null)
return BadRequest(resultatUpload);

try
{
// On récupère l'extension du fichier
string extension = Path.GetExtension(nomFichierSource);

// On crée un nom de fichier unique à l'aide d'un GUID pour éviter
// les collisions si on soumettait plusieurs fois le même nom de fichier
string nomFichier = Guid.NewGuid().ToString();

// Le nom de fichier final complet est créé
string nomFichierStorage = nomFichier + extension;

// Le chemin de sauvegarde est le dossier Storage
string path = Path.Combine(_environment.ContentRootPath,
_configuration["DossierStorage"]!,
nomFichierStorage);

// Écriture du fichier sur le disque
await using FileStream fs = new(path, FileMode.Create);
await file.CopyToAsync(fs);

// Compléter le DTO
resultatUpload.EstSucces = true;
resultatUpload.NomFichier = nomFichierStorage;
resultatUpload.UrlFichier = $"{baseResourceUrl}storage/{nomFichierStorage}";
}
catch (IOException ex)
{
resultatUpload.Erreur = ErreurUpload.Ecriture;
return BadRequest(resultatUpload);
}

// Retour 201 Created avec le DTO
return Created(resultatUpload.UrlFichier!, resultatUpload);
}

Test du contrôleur

Le contrôleur peut être testé à l'aide de Postman. En effet, il est possible dans Postman de choisir pour un paramètre (key) de formulaire (form-data), un type:

  • Text
  • File

Imgur

Si vous faites la sélection pour File comme type, il est ensuite possible de sélectionner un fichier et de l'envoyer avec la requête.

Imgur

Imgur

Si vous faites Send et que tout se passe comme prévu, vous devriez voir qu'un nouveau fichier a été ajouté au dossier Storage 😎

img