文件从 Angular 上传到 ASP.NET 核心
File Upload from Angular to ASP.NET Core
我第一次尝试将文件从 Angular 组件上传到 ASPNET Core 网页,但根本无法正常工作。希望以下代码摘录足以显示正在发生的事情的要点。问题是,虽然我确认传递给 HttpClient 的 post 方法 (frmData) 的参数是有效的,但 ASPNet Core 操作方法从未看到它,并报告 IFormFile 始终为空。
编辑:我之前曾尝试使用 multipart/form-data 作为内容类型,但我在 Kestrel 的内部给出了一个未处理的异常
.我现在意识到这是正确的方法,使用 json 内容类型是我的原始问题的根源。但我不知道从这里去哪里。我从一些谷歌搜索中看到大约有 \billion 个不同的原因导致该异常发生。
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.
如有任何帮助,我们将不胜感激!
Angular 分量:
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;
}
);
});
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 核心控件
// 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)
为假,你的函数 return 是什么?
您将文件作为表单数据发送,因此您需要指定正确的内容类型 header。当前您在 Content-Type
header 中发送 application/json
。即使在调用 API 时也是如此,一开始可能会造成混淆,这是可以理解的。在这种情况下正确的内容类型是 multipart/form-data
。您的 API 没有看到 IFormFile
,因为它认为请求是 JSON。我已经用正确的 content-type header 值修改了您的 Angular 代码。
编辑:原来手动指定一个Content-Type
header会导致边界值不会在header中自动设置价值。相反,简单的解决方案是不要自己添加 header,这将导致自动设置正确的 content-type 和边界值。如果您自己设置 header,您还必须设置边界值。对于大多数情况,将其保留为默认值可能是最好的解决方案。 Link 到 question/answer 指出了这一点。
FormData how to get or set boundary 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 });
}
}
您还可以注意您提供的 HTTP 请求中的 content-disposition,它显示表单数据以及附加文件的类型。希望这可以帮助。我没有启动 Angular 项目来测试您的代码,但 content-type 应该可以解决您的问题。
Edit :我注意到您使用文件名作为文件表单字段的键。您需要为表单字段使用诸如 'file' 之类的键,该键应与控制器代码中的参数名称相匹配。您可以在控制器代码中获取文件的实际文件名,键仅指示文件附加到哪个表单字段。例子
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();
}
我第一次尝试将文件从 Angular 组件上传到 ASPNET Core 网页,但根本无法正常工作。希望以下代码摘录足以显示正在发生的事情的要点。问题是,虽然我确认传递给 HttpClient 的 post 方法 (frmData) 的参数是有效的,但 ASPNet Core 操作方法从未看到它,并报告 IFormFile 始终为空。
编辑:我之前曾尝试使用 multipart/form-data 作为内容类型,但我在 Kestrel 的内部给出了一个未处理的异常 .我现在意识到这是正确的方法,使用 json 内容类型是我的原始问题的根源。但我不知道从这里去哪里。我从一些谷歌搜索中看到大约有 \billion 个不同的原因导致该异常发生。
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.
如有任何帮助,我们将不胜感激!
Angular 分量:
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;
}
);
});
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 核心控件
// 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)
为假,你的函数 return 是什么?
您将文件作为表单数据发送,因此您需要指定正确的内容类型 header。当前您在 Content-Type
header 中发送 application/json
。即使在调用 API 时也是如此,一开始可能会造成混淆,这是可以理解的。在这种情况下正确的内容类型是 multipart/form-data
。您的 API 没有看到 IFormFile
,因为它认为请求是 JSON。我已经用正确的 content-type header 值修改了您的 Angular 代码。
编辑:原来手动指定一个Content-Type
header会导致边界值不会在header中自动设置价值。相反,简单的解决方案是不要自己添加 header,这将导致自动设置正确的 content-type 和边界值。如果您自己设置 header,您还必须设置边界值。对于大多数情况,将其保留为默认值可能是最好的解决方案。 Link 到 question/answer 指出了这一点。
FormData how to get or set boundary 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 });
}
}
您还可以注意您提供的 HTTP 请求中的 content-disposition,它显示表单数据以及附加文件的类型。希望这可以帮助。我没有启动 Angular 项目来测试您的代码,但 content-type 应该可以解决您的问题。
Edit :我注意到您使用文件名作为文件表单字段的键。您需要为表单字段使用诸如 'file' 之类的键,该键应与控制器代码中的参数名称相匹配。您可以在控制器代码中获取文件的实际文件名,键仅指示文件附加到哪个表单字段。例子
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();
}