Upload de arquivo de Angular para ASP.NET Core

Dec 11 2020

Pela primeira vez, estou tentando carregar um arquivo de um componente Angular para uma página da web ASPNET Core e simplesmente não consigo fazê-lo funcionar. Esperançosamente, os trechos de código a seguir serão suficientes para mostrar o essencial do que está acontecendo. O problema é que, embora eu confirme que o parâmetro passado para o método post do HttpClient (frmData) é válido, o método de ação ASPNet Core nunca o vê e relata que IFormFile é sempre nulo.

EDIT: Eu já havia tentado usar multipart / form-data como o tipo de conteúdo, mas dei uma exceção não tratada nas entranhas do Kestrel. Percebi agora que esta é a maneira correta de fazer isso, e usar o tipo de conteúdo json foi a fonte do meu problema ORIGINAL. Mas não sei para onde ir a partir daqui. Vejo, por meio de pesquisas no Google, que há cerca de um bilhão de causas diferentes para a ocorrência dessa exceção.

POST Executando endpoint 'JovenesA.Controllers.StudentssController.PostStudentGradesReport (JAWebAPI)'
04: 55: 38.4853 Info ControllerActionInvoker
POST Rota correspondida com {action = "PostStudentGradesReport", controller = "Becas"}. Executando a ação JovenesA.Controllers.BecasController.PostStudentGradesReport (JAWebAPI)
04: 55: 38.5032 Erro DeveloperExceptionPageMiddleware
POST Ocorreu uma exceção não tratada durante a execução da solicitação.
04: 55: 38.5333 Info WebHost
Solicitação POST concluída em 48.1225ms 500 text / html; charset = utf-8
04: 55: 38.5333 Info Kestrel
 ID de conexão "0HM4UHGE85O17", ID de solicitação "0HM4UHGE85O17: 00000006": o aplicativo foi concluído sem ler todo o corpo da solicitação.

Qualquer ajuda seria muito apreciada!

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

Serviço 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];

Se ajudar, aqui estão os dados que estão sendo enviados na solicitação POST

POST http://192.168.0.16:1099/api/students/student-grades-report HTTP / 1.1
Host: 192.168.0.16:1099
Conexão: keep-alive
Comprimento do conteúdo: 13561
Aceitar: application / json, text / plain, * / *
DNT: 1
Agente do usuário: Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit / 537.36 (KHTML, como Gecko) Chrome / 87.0.4280.88 Safari / 537.36
Tipo de conteúdo: application / json
Origem: http: // localhost: 3000
Referer: http: // localhost: 3000 /
Aceitar-Codificação: gzip, deflate
Aceitar-Idioma: en-US, en; q = 0,9, es-MX; q = 0,8, es; q = 0,7

------ WebKitFormBoundaryBVuZ7IbkjtQAKQ0a
Content-Disposition: form-data; nome = "teste1.PNG"; filename = "test1.PNG"
Tipo de conteúdo: imagem / png

 PNG
 
  [conteúdo binário do arquivo de imagem]  

------ WebKitFormBoundaryBVuZ7IbkjtQAKQ0a--

Respostas

2 jandrew Dec 12 2020 at 09:30

Você está enviando o arquivo como dados de formulário, portanto, é necessário especificar o cabeçalho do tipo de conteúdo correto. Atualmente seu envio application/jsonno Content-Typecabeçalho. Isso é verdade mesmo ao chamar uma API, o que pode ser compreensivelmente confuso no início. O tipo de conteúdo correto neste caso é multipart/form-data. Sua API não está vendo o IFormFileporque acha que a solicitação é JSON. Modifiquei seu código Angular com o valor de cabeçalho de tipo de conteúdo correto.

Editar: Acontece que a especificação manual de um Content-Typecabeçalho fará com que os valores de limite não sejam definidos automaticamente no valor do cabeçalho. Em vez disso, a solução simples é não adicionar o cabeçalho você mesmo, o que resultará no tipo de conteúdo adequado e nos valores de limite a serem definidos automaticamente. Se você mesmo definir o cabeçalho, também terá que definir os valores de limite. Para a maioria das situações, deixar o padrão é provavelmente a melhor solução. Link para pergunta / resposta que aponta isso. FormData como obter ou definir limites em 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 });
    }
}

Você também pode observar a disposição do conteúdo que está na solicitação HTTP que você forneceu, que mostra os dados do formulário junto com o tipo de arquivo anexado. Espero que isto ajude. Não iniciei um projeto Angular para testar seu código, mas o tipo de conteúdo deve corrigir seu problema.

Edit : notei que você está usando o nome do arquivo como a chave para o campo do formulário com o arquivo. Você precisa usar uma chave como apenas 'arquivo' para o campo do formulário, que deve corresponder ao nome do parâmetro no código do controlador. Você pode obter o nome do arquivo real no código do controlador; a chave simplesmente indica a qual campo do formulário o (s) arquivo (s) estão anexados. Exemplo

frmData.append('file', file);

E então para sua ação de controle

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

Não posso garantir que isso funcionará, mas você pode tentar usar o HttpRequest do Angular. Então, em seu serviço angular, tente isto:

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

Observe também que você não deve fazer validação de dados na função que chama o backend Api. Qual será a sua função retornará se if(frmData)for false?