Datei-Upload von Angular zu ASP.NET Core

Dec 11 2020

Zum ersten Mal versuche ich, eine Datei von einer Angular-Komponente auf eine ASPNET Core-Webseite hochzuladen, und kann sie einfach nicht zum Laufen bringen. Hoffentlich reichen die folgenden Code-Auszüge aus, um das Wesentliche zu zeigen. Das Problem ist, dass, obwohl ich bestätige, dass der an die Post-Methode des HttpClient (frmData) übergebene Parameter gültig ist, die ASPNet Core-Aktionsmethode ihn nie sieht und meldet, dass IFormFile immer null ist.

BEARBEITEN: Ich hatte zuvor versucht, mehrteilige / Formulardaten als Inhaltstyp zu verwenden, aber ich gab eine unbehandelte Ausnahme in den Eingeweiden von Kestrel. Mir ist jetzt klar, dass dies der richtige Weg ist, und die Verwendung des JSON-Inhaltstyps war die Ursache für mein ORIGINAL-Problem. Aber ich weiß nicht, wohin ich von hier aus gehen soll. Ich sehe aus einigen Googeln, dass es ungefähr eine Milliarde verschiedene Ursachen für das Auftreten dieser Ausnahme gibt.

POST Ausführen des Endpunkts 'JovenesA.Controllers.StudentssController.PostStudentGradesReport (JAWebAPI)'
04: 55: 38.4853 Info ControllerActionInvoker
POST Route übereinstimmt mit {action = "PostStudentGradesReport", controller = "Becas"}. Ausführen der Aktion JovenesA.Controllers.BecasController.PostStudentGradesReport (JAWebAPI)
04: 55: 38.5032 Fehler DeveloperExceptionPageMiddleware
POST Während der Ausführung der Anforderung ist eine nicht behandelte Ausnahme aufgetreten.
04: 55: 38.5333 Info WebHost
POST-Anfrage abgeschlossen in 48.1225ms 500 text / html; Zeichensatz = utf-8
04: 55: 38.5333 Info Turmfalke
 Verbindungs-ID "0HM4UHGE85O17", Anforderungs-ID "0HM4UHGE85O17: 00000006": Die Anwendung wurde abgeschlossen, ohne den gesamten Anforderungshauptteil zu lesen.

Jede Hilfe wäre sehr dankbar!

Winkelkomponente:

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

Winkelservice:

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

Wenn es hilft, sind hier die Daten, die in der POST-Anfrage gesendet werden

POST http://192.168.0.16:1099/api/students/student-grades-report HTTP / 1.1
Host: 192.168.0.16:1099
Verbindung: am Leben bleiben
Inhaltslänge: 13561
Akzeptieren: application / json, text / plain, * / *
DNT: 1
User-Agent: Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit / 537.36 (KHTML, wie Gecko) Chrome / 87.0.4280.88 Safari / 537.36
Inhaltstyp: application / json
Herkunft: http: // localhost: 3000
Referer: http: // localhost: 3000 /
Accept-Encoding: gzip, entleeren
Akzeptiersprache: en-US, en; q = 0,9, es-MX; q = 0,8, es; q = 0,7

------ WebKitFormBoundaryBVuZ7IbkjtQAKQ0a
Inhaltsdisposition: Formulardaten; name = "test1.PNG"; Dateiname = "test1.PNG"
Inhaltstyp: image / png

 PNG
 
  [binärer Inhalt der Bilddatei]  

------ WebKitFormBoundaryBVuZ7IbkjtQAKQ0a--

Antworten

2 jandrew Dec 12 2020 at 09:30

Sie senden die Datei als Formulardaten, daher müssen Sie den richtigen Inhaltstyp-Header angeben. Derzeit senden Sie application/jsonin der Content-TypeKopfzeile. Dies gilt auch beim Aufrufen einer API, was zunächst verständlicherweise verwirrend sein kann. Der richtige Inhaltstyp ist in diesem Fall multipart/form-data. Ihre API sieht das nicht, IFormFileweil sie glaubt, dass die Anforderung JSON ist. Ich habe Ihren Angular-Code mit dem richtigen Header-Wert für den Inhaltstyp geändert.

Bearbeiten: Es stellt sich heraus, dass die manuelle Angabe eines Content-TypeHeaders dazu führt, dass die Grenzwerte nicht automatisch im Header-Wert festgelegt werden. Stattdessen besteht die einfache Lösung darin, den Header nicht selbst hinzuzufügen, was dazu führt, dass der richtige Inhaltstyp und die richtigen Grenzwerte automatisch festgelegt werden. Wenn Sie den Header selbst festlegen, müssen Sie auch die Grenzwerte festlegen. In den meisten Situationen ist es wahrscheinlich die beste Lösung, die Standardeinstellung beizubehalten. Link zu Frage / Antwort, die darauf hinweist. FormData wie man Grenzen in mehrteiligen / Formulardaten erhält oder setzt - 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 });
    }
}

Sie können auch die Inhaltsdisposition in der von Ihnen angegebenen HTTP-Anforderung notieren, in der Formulardaten zusammen mit dem angehängten Dateityp angezeigt werden. Hoffe das hilft. Ich habe kein Angular-Projekt gestartet, um Ihren Code zu testen, aber der Inhaltstyp sollte Ihr Problem beheben.

Bearbeiten : Ich habe festgestellt, dass Sie den Dateinamen als Schlüssel für das Formularfeld mit der Datei verwenden. Sie müssen einen Schlüssel wie "Datei" für das Formularfeld verwenden, der mit dem Namen des Parameters in Ihrem Controller-Code übereinstimmen sollte. Sie können den tatsächlichen Dateinamen der Datei in Ihrem Controller-Code abrufen. Der Schlüssel gibt lediglich an, an welches Formularfeld die Datei (en) angehängt sind. Beispiel

frmData.append('file', file);

Und dann für Ihre Controller-Aktion

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

Ich kann nicht garantieren, dass dies funktioniert, aber Sie können versuchen, Angulars HttpRequest zu verwenden. Versuchen Sie also in Ihrem Winkelservice Folgendes:

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

Beachten Sie auch, dass Sie in der Funktion, die die Backend-API aufruft, keine Datenüberprüfung durchführen sollten. Was wird Ihre Funktion zurückgeben, wenn sie if(frmData)falsch ist?