文件从 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-Typeheader会导致边界值不会在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(); 
}