Caricamento di file da Angular a ASP.NET Core

Dec 11 2020

Per la prima volta cerco di caricare un file da un componente Angular a una pagina Web ASPNET Core e semplicemente non riesco a farlo funzionare. Si spera che i seguenti estratti di codice siano sufficienti per mostrare gli elementi essenziali di ciò che sta accadendo. Il problema è che, sebbene confermi che il parametro passato al metodo post di HttpClient (frmData) è valido, il metodo di azione ASPNet Core non lo vede mai e segnala che IFormFile è sempre nullo.

EDIT: avevo già provato a utilizzare multipart / form-data come tipo di contenuto ma ho dato un'eccezione non gestita nelle viscere di Kestrel. Mi rendo conto ora che questo è il modo corretto per farlo e l'utilizzo del tipo di contenuto json è stata la fonte del mio problema ORIGINALE. Ma non so dove andare da qui. Vedo da alcune ricerche su Google che ci sono circa un miliardo di cause diverse per il verificarsi di tale eccezione.

POST Endpoint in esecuzione "JovenesA.Controllers.StudentssController.PostStudentGradesReport (JAWebAPI)"
04: 55: 38.4853 Info Controller ActionInvoker
POST Route corrispondente a {action = "PostStudentGradesReport", controller = "Becas"}. Azione in esecuzione JovenesA.Controllers.BecasController.PostStudentGradesReport (JAWebAPI)
04: 55: 38.5032 Errore DeveloperExceptionPageMiddleware
POST Si è verificata un'eccezione non gestita durante l'esecuzione della richiesta.
04: 55: 38.5333 Informazioni WebHost
Richiesta POST terminata in 48.1225ms 500 text / html; charset = utf-8
04: 55: 38.5333 Informazioni su Kestrel
 ID connessione "0HM4UHGE85O17", ID richiesta "0HM4UHGE85O17: 00000006": l'applicazione completata senza leggere l'intero corpo della richiesta.

Qualsiasi aiuto sarebbe molto apprezzato!

Componente angolare:

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

Servizio angolare:

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

ASPNET Core Controlle

// 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];

Se aiuta, ecco i dati che vengono inviati nella richiesta POST

POST http://192.168.0.16:1099/api/students/student-grades-report HTTP / 1.1
Host: 192.168.0.16:1099
Connessione: keep-alive
Lunghezza contenuto: 13561
Accetta: application / json, text / plain, * / *
DNT: 1
Agente utente: Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit / 537.36 (KHTML, come Gecko) Chrome / 87.0.4280.88 Safari / 537.36
Tipo di contenuto: application / json
Origine: http: // localhost: 3000
Referer: http: // localhost: 3000 /
Accetta codifica: gzip, deflate
Accept-Language: en-US, en; q = 0.9, es-MX; q = 0.8, es; q = 0.7

------ WebKitFormBoundaryBVuZ7IbkjtQAKQ0a
Content-Disposition: form-data; name = "test1.PNG"; nomefile = "test1.PNG"
Tipo di contenuto: immagine / png

 PNG
 
  [contenuto binario del file immagine]  

------ WebKitFormBoundaryBVuZ7IbkjtQAKQ0a--

Risposte

2 jandrew Dec 12 2020 at 09:30

Stai inviando il file come dati del modulo, quindi devi specificare l'intestazione del tipo di contenuto corretto. Attualmente il tuo invio application/jsonin Content-Typeintestazione. Ciò è vero anche quando si chiama un'API, il che può comprensibilmente creare confusione all'inizio. Il tipo di contenuto corretto in questo caso è multipart/form-data. La tua API non vede il IFormFileperché pensa che la richiesta sia JSON. Ho modificato il tuo codice angolare con il valore di intestazione del tipo di contenuto corretto.

Modifica: si scopre che specificando manualmente Content-Typeun'intestazione i valori limite non verranno impostati automaticamente nel valore dell'intestazione. Invece, la soluzione semplice è non aggiungere l'intestazione da soli, il che comporterà l'impostazione automatica del tipo di contenuto e dei valori limite corretti. Se imposti tu stesso l'intestazione, dovrai anche impostare i valori limite. Per la maggior parte delle situazioni, lasciarlo predefinito è probabilmente la soluzione migliore. Link alla domanda / risposta che lo sottolinea. FormData come ottenere o impostare il confine in 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 });
    }
}

Puoi anche notare la disposizione del contenuto che si trova sulla richiesta HTTP che hai fornito, che mostra i dati del modulo insieme al tipo di file allegato. Spero che sia di aiuto. Non ho avviato un progetto Angular per testare il tuo codice, ma il tipo di contenuto dovrebbe risolvere il tuo problema.

Modifica : ho notato che stai utilizzando il nome del file come chiave per il campo del modulo con il file. È necessario utilizzare una chiave come "file" per il campo del modulo, che dovrebbe corrispondere al nome del parametro nel codice del controller. È possibile ottenere il nome file effettivo del file all'interno del codice del controller, la chiave indica semplicemente il campo del modulo a cui sono allegati i file. Esempio

frmData.append('file', file);

E poi per l'azione del controller

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

Non posso garantire che funzioni, ma puoi provare a utilizzare HttpRequest di Angular. Quindi nel tuo servizio angolare, prova questo:

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

Si noti inoltre che non è necessario eseguire la convalida dei dati nella funzione che richiama l'api di backend. Qual è la tua funzione che restituirà se if(frmData)è falso?