Впервые я пытаюсь загрузить файл из компонента 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--
спасибо - см. комментарий, который я добавил к моему первоначальному вопросу по этому поводу
Я не могу гарантировать, что это сработает, но вы можете попробовать использовать 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 не является нулевым на стороне клиента перед вызовом.
Вы отправляете файл как данные формы, поэтому вам нужно указать правильный заголовок типа контента. В настоящее время ваша отправка 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 Можете ли вы предоставить остальную часть кода действия вашего контроллера? Похоже, что в какой-то момент внутри него создается исключение, я не думаю, что это связано с Kestrel.
@ckapilla Кроме того, похоже, что вы используете имя файла в качестве ключа для поля файла в форме. Вам нужно использовать один и тот же ключ между кодом Angular и API. Например, PostStudentGradesReport([FromForm] IFormFile file);
и ваш код Angular должны использовать один и тот же ключ, т. е. frmData.append('file', file);
обратите внимание на изменение ключа на «файл». Вы можете получить фактическое имя файла в коде вашего контроллера, ключ не указывает имя файла.
что касается вашего первого комментария, ошибка определенно возникла из-за внутренностей Kestrel. Если вы погуглите, вы увидите, что существует почти бесконечное количество причин для получения этой ошибки, но все они находятся внутри обработки Kestrel. Кроме того, мой код контроллера никогда не вводится, когда возникает исключение.
что касается вашего второго комментария, это очень полезно (и очень очевидно, как только вы указали на это!) Я обнаружил, что это было одно из двух изменений, которые мне нужно было внести. Во-вторых, добавление раздела «boundary---------------------xxxxxxxxxxxx» в заголовок Content-type. Это отсутствие было причиной исключения. Теперь мне просто нужно выяснить, КАК добавить это предложение....
@ckapilla Вы тестировали его после изменения ключа поля формы? Я видел, как кто-то столкнулся с ошибкой границы, потому что он не сопоставил имя параметра с ключом поля формы. Вы должны увидеть InvalidDataException
, если это вызвано проблемой границы Multipart. Похоже, вы используете страницу исключений для разработки, если да, то каков конкретный вывод? Ваша длина содержимого выглядит в пределах ограничений Kestrel.
да, используя Fiddler, я протестировал исправленную проблему с границей =, и это по-прежнему вызывало проблему с нулевым параметром, но исправляло исключение. Затем, изменив ключ поля формы в соответствии с вашим предложением, все сработало полностью. В моей программе, похоже, я могу просто указать border= как часть Content-type, и это будет использовано.
@ckapilla Что касается проблемы с границами, оказывается, я кое-что упустил, и решение на самом деле очень простое. Когда вы вручную указываете заголовок типа содержимого, граничные значения не вычисляются автоматически и не добавляются к значению заголовка. Вместо этого, если вы оставите его по умолчанию и не добавите вручную заголовок Content-Type
к объекту запроса, они будут установлены автоматически. Я обновил свой ответ, включив в него эту дополнительную информацию, а также ссылку на другой вопрос, который указывает на это.
вау, большое спасибо за этот последний комментарий - он действительно работает, а все остальное, что я пробовал, не сработало. Этот пост, на который вы ссылаетесь, был волшебным, но я почему-то так и не нашел его самостоятельно.
Content-Type: application/json
Вот это ваша проблема. Вы отправляете файл как данные формы, вам нужно указать правильный тип контента в коде запроса Angular. Правильным типом в этом случае будетmultipart/form-data
. Это верно, даже когда вы вызываете API, стандартный метод загрузки файлов заключается в отправке их в виде данных формы, как вы это делаете. Просто нужно указать правильный тип контента.