Téléchargement de fichiers depuis Angular vers ASP.NET Core

Dec 11 2020

Pour la première fois, j'essaie de télécharger un fichier d'un composant angulaire vers une page Web ASPNET Core, et je ne peux tout simplement pas le faire fonctionner. Espérons que les extraits de code suivants suffiront à montrer l'essentiel de ce qui se passe. Le problème est que même si je confirme que le paramètre passé à la méthode de publication du HttpClient (frmData) est valide, la méthode d'action ASPNet Core ne le voit jamais et signale que IFormFile est toujours nul.

EDIT: J'avais déjà essayé d'utiliser multipart / form-data comme type de contenu mais j'ai donné une exception non gérée dans les tripes de Kestrel. Je me rends compte maintenant que c'est la bonne façon de le faire, et l'utilisation du type de contenu json était la source de mon problème ORIGINAL. Mais je ne sais pas où aller d'ici. Je vois sur Google, il y a environ un milliard de causes différentes pour que cette exception se produise.

POST Exécution du point de terminaison 'JovenesA.Controllers.StudentssController.PostStudentGradesReport (JAWebAPI)'
04: 55: 38.4853 Info ControllerActionInvoker
Route POST correspondant à {action = "PostStudentGradesReport", controller = "Becas"}. Exécution de l'action JovenesA.Controllers.BecasController.PostStudentGradesReport (JAWebAPI)
04: 55: 38.5032 Erreur DeveloperExceptionPageMiddleware
POST Une exception non gérée s'est produite lors de l'exécution de la demande.
04: 55: 38.5333 Info WebHost
Requête POST terminée en 48,1225 ms 500 text / html; jeu de caractères = utf-8
04: 55: 38.5333 Info Kestrel
 Identifiant de connexion "0HM4UHGE85O17", Identifiant de requête "0HM4UHGE85O17: 00000006": l'application s'est terminée sans lire le corps de la requête dans son intégralité.

Toute aide serait grandement appréciée!

Composant angulaire:

fileEntry.file((file: File) => {
      console.log('fileEntry relativePath: ' + currFile.relativePath);
      console.log('filEntry.name: ', file.name);
      console.log('filEntry.size: ', file.size);

      const frmData = new FormData();
      frmData.append(file.name, file);

      this.studentData.uploadStudentGradesReport(file.name, frmData).subscribe(
        () => {
          this.successMessage = 'Changes were saved successfully.';
          window.scrollTo(0, 0);
          window.setTimeout(() => {
            this.successMessage = '';
          }, 3000);
        },
        (error) => {
          this.errorMessage = error;
        }
      );
    });

Service angulaire:

public uploadStudentGradesReport(filename: string, frmData: FormData): Observable<any> {
    const url = this.WebApiPrefix + 'students/' + 'student-grades-report';
    const headers = new HttpHeaders().set('Content-Type', 'application/json');
    if (frmData) {
      console.log('ready to post ' + url + ' filename: ' + filename + ' options ' + headers);
      return this.http.post(url, frmData, { headers });
    }
}

Contrôle ASPNET Core

// POST api/students/student-grades-report
[HttpPost("student-grades-report", Name = "PostStudentGradseReportRoute")]
//[ValidateAntiForgeryToken]
[ProducesResponseType(typeof(GradesGivenEntryApiResponse), 200)]
[ProducesResponseType(typeof(GradesGivenEntryApiResponse), 400)]
public async Task<ActionResult> PostStudentGradesReport([FromForm] IFormFile myFile)
{
    _Logger.LogInformation("Post StudentGradesReport  ");

    if (myFile != null)
    {
        var totalSize = myFile.Length;
        var fileBytes = new byte[myFile.Length];

Si cela vous aide, voici les données qui sont envoyées dans la requête POST

POST http://192.168.0.16:1099/api/students/student-grades-report HTTP / 1.1
Hôte: 192.168.0.16:1099
Connexion: keep-alive
Contenu-Longueur: 13561
Accepter: application / json, text / plain, * / *
DNT: 1
User-Agent: Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit / 537.36 (KHTML, comme Gecko) Chrome / 87.0.4280.88 Safari / 537.36
Content-Type: application / json
Origine: http: // localhost: 3000
Référent: http: // localhost: 3000 /
Accept-Encoding: gzip, dégonfler
Accept-Language: en-US, en; q = 0.9, es-MX; q = 0.8, es; q = 0.7

------ WebKitFormBoundaryBVuZ7IbkjtQAKQ0a
Content-Disposition: données de formulaire; name = "test1.PNG"; filename = "test1.PNG"
Type de contenu: image / png

 PNG
 
  [contenu binaire du fichier image]  

------ WebKitFormBoundaryBVuZ7IbkjtQAKQ0a--

Réponses

2 jandrew Dec 12 2020 at 09:30

Vous envoyez le fichier sous forme de données de formulaire, vous devez donc spécifier l'en-tête de type de contenu correct. Actuellement votre envoi application/jsondans l'en- Content-Typetête. Cela est vrai même lors de l'appel d'une API, ce qui peut naturellement prêter à confusion au début. Le type de contenu correct dans ce cas est multipart/form-data. Votre API ne voit pas le IFormFilecar elle pense que la requête est JSON. J'ai modifié votre code angulaire avec la valeur d'en-tête de type de contenu correcte.

Modifier: il s'avère que la spécification manuelle d'un en- Content-Typetête fera que les valeurs de limite ne seront pas définies automatiquement dans la valeur de l'en-tête. Au lieu de cela, la solution simple consiste à ne pas ajouter l'en-tête vous-même, ce qui entraînera la définition automatique du type de contenu et des valeurs de limite appropriés. Si vous définissez vous-même l'en-tête, vous devrez également définir les valeurs limites. Dans la plupart des situations, le laisser par défaut est probablement la meilleure solution. Lien vers la question / réponse qui le souligne. FormData Comment obtenir ou définir une limite dans multipart / form-data - Angular

public uploadStudentGradesReport(filename: string, frmData: FormData): Observable<any> {
    const url = this.WebApiPrefix + 'students/' + 'student-grades-report';
    const headers = new HttpHeaders().set('Content-Type', 'multipart/form-data');
    if (frmData) {
      console.log('ready to post ' + url + ' filename: ' + filename + ' options ' + headers);
      return this.http.post(url, frmData, { headers });
    }
}

Vous pouvez également noter la disposition du contenu qui se trouve sur la requête HTTP que vous avez fournie, qui affiche les données du formulaire ainsi que le type de fichier joint. J'espère que cela t'aides. Je n'ai pas lancé de projet Angular pour tester votre code, mais le type de contenu devrait résoudre votre problème.

Edit : J'ai remarqué que vous utilisez le nom de fichier comme clé pour le champ de formulaire avec le fichier. Vous devez utiliser une clé telle que «fichier» pour le champ de formulaire, qui doit correspondre au nom du paramètre dans le code de votre contrôleur. Vous pouvez obtenir le nom de fichier réel du fichier dans le code de votre contrôleur, la clé indique simplement à quel champ de formulaire le (s) fichier (s) sont attachés. Exemple

frmData.append('file', file);

Et puis pour votre action de contrôleur

public async Task<IActionResult> PostStudentGradesReport([FromForm] IFormFile file)
{
    if (file.Length <= 0 || file.ContentType is null) return BadRequest();
    var actualFileName = file.FileName;

    using (var stream = file.OpenReadStream())
    {
        // Process file...
    }
    
    return Ok(); 
}
1 Coco Dec 11 2020 at 23:02

Je ne peux pas garantir que cela fonctionnera, mais vous pouvez essayer d'utiliser HttpRequest d'Angular. Donc, dans votre service angulaire, essayez ceci:

const request = new HttpRequest (
    'POST',
     url, // http://localhost/your_endpoint
     frmData,
     { withCredentials: false }
);
    
return this.http.request(request);

Notez également que vous ne devez pas effectuer de validation des données dans la fonction qui appelle l'API backend. Quelle est votre fonction retournera si elle if(frmData)est fausse?