Przesyłanie plików z Angular do ASP.NET Core

Dec 11 2020

Po raz pierwszy próbuję przesłać plik ze składnika Angular na stronę internetową ASPNET Core i po prostu nie mogę go uruchomić. Mamy nadzieję, że poniższe fragmenty kodu wystarczą, aby pokazać podstawy tego, co się dzieje. Problem polega na tym, że chociaż potwierdzam, że przekazany parametr do metody post HttpClient (frmData) jest prawidłowy, metoda akcji ASPNet Core nigdy go nie widzi i zgłasza, że ​​IFormFile jest zawsze null.

EDYCJA: Wcześniej próbowałem użyć multipart / form-data jako typu zawartości, ale dałem nieobsługiwany wyjątek w wnętrznościach Kestrel. Teraz zdaję sobie sprawę, że jest to właściwy sposób, a użycie typu zawartości json było źródłem mojego ORYGINALNEGO problemu. Ale nie wiem, dokąd mam się udać. Z niektórych wyników wyszukiwania w Google wynika, że ​​istnieje około \ miliarda różnych przyczyn wystąpienia tego wyjątku.

POST Wykonanie punktu końcowego „JovenesA.Controllers.StudentssController.PostStudentGradesReport (JAWebAPI)”
04: 55: 38.4853 Informacje ControllerActionInvoker
Trasa POST dopasowana do {action = "PostStudentGradesReport", controller = "Becas"}. Wykonywanie akcji JovenesA.Controllers.BecasController.PostStudentGradesReport (JAWebAPI)
04: 55: 38.5032 Błąd DeveloperExceptionPageMiddleware
POST Wystąpił nieobsługiwany wyjątek podczas wykonywania żądania.
04: 55: 38.5333 Informacje WebHost
Żądanie POST zakończone 48.1225ms 500 text / html; charset = utf-8
04: 55: 38.5333 Informacje Kestrel
 Identyfikator połączenia „0HM4UHGE85O17”, identyfikator żądania „0HM4UHGE85O17: 00000006”: aplikacja została zakończona bez wczytania całej treści żądania.

Każda pomoc byłaby bardzo mile widziana!

Komponent kątowy:

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

Usługa Angular:

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

Jeśli to pomoże, oto dane, które są wysyłane w żądaniu POST

POST http://192.168.0.16:1099/api/students/student-grades-report HTTP / 1.1
Host: 192.168.0.16:1099
Połączenie: utrzymuj przy życiu
Długość treści: 13561
Zaakceptuj: aplikacja / json, tekst / zwykły, * / *
DNT: 1
User-Agent: Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit / 537.36 (KHTML, jak Gecko) Chrome / 87.0.4280.88 Safari / 537.36
Content-Type: application / json
Pochodzenie: http: // localhost: 3000
Referer: http: // localhost: 3000 /
Accept-Encoding: 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"; filename = "test1.PNG"
Content-Type: image / png

 PNG
 
  [binarna zawartość pliku obrazu]  

------ WebKitFormBoundaryBVuZ7IbkjtQAKQ0a--

Odpowiedzi

2 jandrew Dec 12 2020 at 09:30

Wysyłasz plik jako dane formularza, więc musisz określić poprawny nagłówek typu zawartości. Aktualnie Twoja wysyłka application/jsonw Content-Typenagłówku. Dzieje się tak nawet podczas wywoływania interfejsu API, co na początku może być zrozumiałe. W tym przypadku prawidłowy typ zawartości to multipart/form-data. Twój interfejs API nie widzi elementu, IFormFileponieważ uważa, że ​​żądanie to JSON. Zmodyfikowałem Twój kod Angular z poprawną wartością nagłówka typu zawartości.

Edycja: Okazuje się, że ręczne określenie Content-Typenagłówka spowoduje, że wartości graniczne nie zostaną ustawione automatycznie w wartości nagłówka. Zamiast tego prostym rozwiązaniem jest samodzielne dodawanie nagłówka, co spowoduje automatyczne ustawienie odpowiedniego typu treści i wartości granicznych. Jeśli sam ustawisz nagłówek, będziesz musiał również ustawić wartości graniczne. W większości sytuacji pozostawienie wartości domyślnej jest prawdopodobnie najlepszym rozwiązaniem. Link do pytania / odpowiedzi, która na to wskazuje. FormData, jak pobrać lub ustawić granicę w 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 });
    }
}

Możesz również zanotować dyspozycję treści, która znajduje się w podanym żądaniu HTTP, która zawiera dane formularza wraz z typem załączonego pliku. Mam nadzieję że to pomoże. Nie uruchomiłem projektu Angular, aby przetestować Twój kod, ale typ zawartości powinien rozwiązać problem.

Edycja : zauważyłem, że używasz nazwy pliku jako klucza dla pola formularza z plikiem. W polu formularza musisz użyć klucza takiego jak „plik”, który powinien odpowiadać nazwie parametru w kodzie kontrolera. Możesz uzyskać rzeczywistą nazwę pliku w kodzie kontrolera, klucz po prostu wskazuje, do którego pola formularza są dołączone pliki. Przykład

frmData.append('file', file);

A potem do akcji kontrolera

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

Nie mogę zagwarantować, że to zadziała, ale możesz spróbować użyć HttpRequest Angulara. Więc w swojej usłudze kątowej spróbuj tego:

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

Pamiętaj również, że nie powinieneś przeprowadzać walidacji danych w funkcji, która wywołuje interfejs API zaplecza. Jaka funkcja zwróci, jeśli if(frmData)jest fałszywa?