Загрузка файла из Angular в ASP.NET Core

Впервые я пытаюсь загрузить файл из компонента Angular на веб-страницу ASPNET Core и просто не могу заставить его работать. Надеемся, что следующих фрагментов кода будет достаточно, чтобы показать суть происходящего. Проблема в том, что, хотя я подтверждаю, что переданный параметр в метод post HttpClient (frmData) действителен, метод действия ASPNet Core никогда не видит его и сообщает, что IFormFile всегда имеет значение null.

Обновлено: ранее я пытался использовать multipart/form-data в качестве типа контента, но я дал необработанное исключение в кишках Kestrel. . Теперь я понимаю, что это правильный способ сделать это, и использование типа содержимого json было источником моей ИСХОДНОЙ проблемы. Но я не знаю, куда идти отсюда. Я вижу из некоторых поисковых запросов, что существует около миллиарда различных причин возникновения этого исключения.

POST Executing endpoint 'JovenesA.Controllers.StudentssController.PostStudentGradesReport (JAWebAPI)'
04:55:38.4853 Info ControllerActionInvoker
POST Route matched with {action = "PostStudentGradesReport", controller = "Becas"}. Executing action JovenesA.Controllers.BecasController.PostStudentGradesReport (JAWebAPI)
04:55:38.5032 Error DeveloperExceptionPageMiddleware
POST An unhandled exception has occurred while executing the request.
04:55:38.5333 Info WebHost
POST Request finished in 48.1225ms 500 text/html; charset=utf-8
04:55:38.5333 Info Kestrel
 Connection id "0HM4UHGE85O17", Request id "0HM4UHGE85O17:00000006": the application completed without reading the entire request body.

Любая помощь будет принята с благодарностью!

Угловой компонент:

fileEntry.file((file: File) => {
      console.info('fileEntry relativePath: ' + currFile.relativePath);
      console.info('filEntry.name: ', file.name);
      console.info('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;
        }
      );
    });

Угловой сервис:

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.info('ready to post ' + url + ' filename: ' + filename + ' options ' + headers);
      return this.http.post(url, frmData, { headers });
    }
}

Основной контроль ASPNET

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

Если это поможет, вот данные, которые отправляются в запросе POST

POST http://192.168.0.16:1099/api/students/student-grades-report HTTP/1.1
Host: 192.168.0.16:1099
Connection: keep-alive
Content-Length: 13561
Accept: application/json, text/plain, */*
DNT: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Content-Type: application/json
Origin: 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
 
  [ binary contents of the image file ]  

------WebKitFormBoundaryBVuZ7IbkjtQAKQ0a--
Content-Type: application/json Вот это ваша проблема. Вы отправляете файл как данные формы, вам нужно указать правильный тип контента в коде запроса Angular. Правильным типом в этом случае будет multipart/form-data. Это верно, даже когда вы вызываете API, стандартный метод загрузки файлов заключается в отправке их в виде данных формы, как вы это делаете. Просто нужно указать правильный тип контента.
jdewerth 12.12.2020 10:10

спасибо - см. комментарий, который я добавил к моему первоначальному вопросу по этому поводу

ckapilla 12.12.2020 11:58
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
3
2
2 141
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я не могу гарантировать, что это сработает, но вы можете попробовать использовать Angular HttpRequest. Итак, в вашем сервисе angular попробуйте следующее:

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

Также обратите внимание, что вы не должны выполнять проверку данных в функции, которая вызывает серверный API. Что вернет ваша функция, если if (frmData) ложно?

Спасибо за предложение; но, увы, данные все еще не попадают в контроллер ASPNet. Что касается проверки данных, вы правы, я просто имею это временно, чтобы доказать, что frmData не является нулевым на стороне клиента перед вызовом.

ckapilla 12.12.2020 02:10
Ответ принят как подходящий

Вы отправляете файл как данные формы, поэтому вам нужно указать правильный заголовок типа контента. В настоящее время ваша отправка application/json в шапке Content-Type. Это верно даже при вызове API, что по понятным причинам может поначалу сбивать с толку. Правильный тип контента в этом случае — multipart/form-data. Ваш API не видит IFormFile, потому что считает запрос JSON. Я изменил ваш код Angular с правильным значением заголовка типа контента.

Редактировать: оказывается, что указание заголовка Content-Type вручную приведет к тому, что граничные значения не будут автоматически установлены в значении заголовка. Вместо этого простое решение состоит в том, чтобы не добавлять заголовок самостоятельно, что приведет к автоматической установке правильного типа содержимого и граничных значений. Если вы устанавливаете заголовок самостоятельно, вам также придется установить граничные значения. В большинстве ситуаций оставить его по умолчанию, вероятно, лучшее решение. Ссылка на вопрос/ответ, который указывает на это. FormData как получить или установить границу в 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.info('ready to post ' + url + ' filename: ' + filename + ' options ' + headers);
      return this.http.post(url, frmData, { headers });
    }
}

Вы также можете отметить расположение содержимого в предоставленном вами HTTP-запросе, в котором отображаются данные формы вместе с типом прикрепленного файла. Надеюсь это поможет. Я не запускал проект Angular для проверки вашего кода, но тип содержимого должен решить вашу проблему.

Редактировать: я заметил, что вы используете имя файла в качестве ключа для поля формы с файлом. Вам нужно использовать ключ, такой как просто «файл», для поля формы, который должен соответствовать имени параметра в коде вашего контроллера. Вы можете получить фактическое имя файла в коде вашего контроллера, ключ просто указывает, к какому полю формы прикреплены файлы. Пример

frmData.append('file', file);

А затем для вашего действия контроллера

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

спасибо за ваш вклад, пожалуйста, посмотрите мое редактирование исходного вопроса по этому поводу

ckapilla 12.12.2020 12:13

Я понял, что должен принять это как ответ, поскольку он разрешает мой конкретный вопрос о нулевом параметре, хотя мне еще нужно поработать, чтобы загрузить загрузку. Но теперь я знаю, где искать.

ckapilla 12.12.2020 15:44

@ckapilla Можете ли вы предоставить остальную часть кода действия вашего контроллера? Похоже, что в какой-то момент внутри него создается исключение, я не думаю, что это связано с Kestrel.

jdewerth 12.12.2020 19:32

@ckapilla Кроме того, похоже, что вы используете имя файла в качестве ключа для поля файла в форме. Вам нужно использовать один и тот же ключ между кодом Angular и API. Например, PostStudentGradesReport([FromForm] IFormFile file); и ваш код Angular должны использовать один и тот же ключ, т. е. frmData.append('file', file); обратите внимание на изменение ключа на «файл». Вы можете получить фактическое имя файла в коде вашего контроллера, ключ не указывает имя файла.

jdewerth 12.12.2020 20:17

что касается вашего первого комментария, ошибка определенно возникла из-за внутренностей Kestrel. Если вы погуглите, вы увидите, что существует почти бесконечное количество причин для получения этой ошибки, но все они находятся внутри обработки Kestrel. Кроме того, мой код контроллера никогда не вводится, когда возникает исключение.

ckapilla 12.12.2020 21:48

что касается вашего второго комментария, это очень полезно (и очень очевидно, как только вы указали на это!) Я обнаружил, что это было одно из двух изменений, которые мне нужно было внести. Во-вторых, добавление раздела «boundary---------------------xxxxxxxxxxxx» в заголовок Content-type. Это отсутствие было причиной исключения. Теперь мне просто нужно выяснить, КАК добавить это предложение....

ckapilla 12.12.2020 21:52

@ckapilla Вы тестировали его после изменения ключа поля формы? Я видел, как кто-то столкнулся с ошибкой границы, потому что он не сопоставил имя параметра с ключом поля формы. Вы должны увидеть InvalidDataException, если это вызвано проблемой границы Multipart. Похоже, вы используете страницу исключений для разработки, если да, то каков конкретный вывод? Ваша длина содержимого выглядит в пределах ограничений Kestrel.

jdewerth 12.12.2020 21:57

да, используя Fiddler, я протестировал исправленную проблему с границей =, и это по-прежнему вызывало проблему с нулевым параметром, но исправляло исключение. Затем, изменив ключ поля формы в соответствии с вашим предложением, все сработало полностью. В моей программе, похоже, я могу просто указать border= как часть Content-type, и это будет использовано.

ckapilla 12.12.2020 22:01

@ckapilla Что касается проблемы с границами, оказывается, я кое-что упустил, и решение на самом деле очень простое. Когда вы вручную указываете заголовок типа содержимого, граничные значения не вычисляются автоматически и не добавляются к значению заголовка. Вместо этого, если вы оставите его по умолчанию и не добавите вручную заголовок Content-Type к объекту запроса, они будут установлены автоматически. Я обновил свой ответ, включив в него эту дополнительную информацию, а также ссылку на другой вопрос, который указывает на это.

jdewerth 12.12.2020 22:33

вау, большое спасибо за этот последний комментарий - он действительно работает, а все остальное, что я пробовал, не сработало. Этот пост, на который вы ссылаетесь, был волшебным, но я почему-то так и не нашел его самостоятельно.

ckapilla 12.12.2020 22:51

Другие вопросы по теме