O ASP.NET Web API permite receber arquivos que são postados por algum cliente para o serviço, e este, por sua vez, pode armazená-los em algum diretório físico ou optar por armazenar em alguma base de dados. Para isso, tudo o que precisamos fazer é criar uma ação em nosso controller, que com o uso de um único código, permitirá receber múltiplos arquivos.
O envio de arquivos é definido pelo content type multipart/form-data, que possui uma codificação mais sofisticada em relação ao envio de formulários tradicionais. Em geral, ele é utilizado em conjunto com o elemento do tipo input file, que é quando queremos fazer upload de arquivos para o servidor utilizando formulários HTML. Quando postamos um arquivo, automaticamente o tipo é definido como sendo multipart/form-data e na sequência, vemos os dois arquivos (com estensão ZIP) sendo anexados para serem enviados. Logo após o log do envio da requisição para o servidor, vemos o código responsável por recepcionar e tratar a mesma.
POST http://localhost:6764/api/Teste/Upload HTTP/1.1
Content-Type: multipart/form-data; boundary=————————-acebdf13572468
User-Agent: Fiddler
Host: localhost:6764
Content-Length: 148996
—————————acebdf13572468
Content-Disposition: form-data; name=”fieldNameHere”; filename=”Arquivo1.zip”
Content-Type: application/octet-stream
— Omitidos por questões de espaço —
—————————acebdf13572468
Content-Disposition: form-data; name=”fieldNameHere”; filename=”Arquivo2.zip”
Content-Type: application/octet-stream
[HttpPost]
public async Task<HttpResponseMessage> Upload()
{
var provider =
new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath(“~/Uploads”));
return await Request.Content.ReadAsMultipartAsync(provider).ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
return Request.CreateResponse(HttpStatusCode.OK);
});
}
A ação Upload do controller Teste para qual realizamos o post, não recebe nenhum parâmetro; ele é extraído do corpo da mensagem ao executar o método ReadAsMultipartAsync, que assincronamente lê e “materializa” os arquivos, salvando automaticamente no caminho informado no construtor do provider. Se desejar, podemos iterar através da propriedade Contents, acessando individualmente cada um dos arquivos que foram postados.
Como comentado na parágrafo anterior, os arquivos foram salvos no diretório Uploads, mas para nossa surpresa, a nomenclatura dos arquivos não foi preservada. Isso se deve ao fato de que o ASP.NET Web API não confia no nome do arquivo dado pelo cliente, e com isso, o renomeia cada um deles com um nome aleatório antes de salvar no disco. Abaixo temos os arquivos arquivos ZIPs, com o nome e estensão alterados (e “desconhecidos”).
Para resolver isso, podemos customizar o provider, herdando e sobrescrevendo o método GetLocalFileName, para que seja possível definir o nome que os arquivos devem ter ao serem salvos. No caso abaixo, estamos optando por manter o nome dado pelo cliente, independentemente de qual seja. Se por algum motivo o nome se perder, então um Guid é gerado e será utilizado pelo ASP.NET Web API para nomear aquele arquivo. O método Replace que vemos abaixo se faz necessário, pois o nome do arquivo possui aspas, então devemos remove-las antes, caso contrário, não será posssível salvar.
public class CustomMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
public CustomMultipartFormDataStreamProvider(string rootPath)
: base(rootPath) { }
public override string GetLocalFileName(HttpContentHeaders headers)
{
var filename = headers.ContentDisposition.FileName;
return !string.IsNullOrWhiteSpace(filename) ?
filename.Replace(“””, string.Empty) :
Guid.NewGuid().ToString();
}
}
Depois de customizado, basta utilizar esta nova classe ao invés do MultipartFormDataStreamProvider (versão original). Ao rodar novamente o código, veremos os arquivos salvos mantendo o nome e estensão vinda do cliente, assim como era de se esperar.
Olá Israel, em sua opinião, seria errado serializar um arquivo para enviá-lo como JSON?
Sei que ao transformá-lo num base64 há um aumento significativo no tamanho do arquivo, porém quando lidamos só com arquivos pequenos não vejo problemas. O que você acha?
Abs
Boas Fabiano,
Não entendi bem. Poderia, por favor, explicar melhor?
Olá Israel,
Meu amigo, queria apenas te dizer que seu artigo foi muito útil pra mim e me salvou de uma situação complicada, pois estava tendo problemas em salvar meus uploads localmente… minha rotina não funcionava com imagens, apenas funcionava com arquivos pdf… e esse artigo foi simplesmente o salvador !!!
Parabéns meu amigo e muito obrigado !!!
Ola Israel,
Você teria o exemplo de como consumir esta API, preciso enviar varios arquivos para a API (Esta do seu exemplo é praticamente que fiz), porem não estou conseguindo.