Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Parte 2: Carregamento de Ficheiros e MIME Multiparte
Este tutorial mostra como carregar ficheiros para uma API web. Descreve também como processar dados MIME em múltiplas partes.
Observação
Aqui está um exemplo de formulário HTML para carregar um ficheiro:
<form name="form1" method="post" enctype="multipart/form-data" action="api/upload">
<div>
<label for="caption">Image Caption</label>
<input name="caption" type="text" />
</div>
<div>
<label for="image1">Image File</label>
<input name="image1" type="file" />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
Este formulário contém um controlo de entrada de texto e um controlo de entrada de ficheiros. Quando um formulário contém um controlo de entrada de ficheiro, o atributo enctype deve ser sempre "multipart/form-data", que especifica que o formulário será enviado como uma mensagem MIME multipart.
O formato de uma mensagem MIME multiparte é mais fácil de entender olhando para um pedido de exemplo:
POST http://localhost:50460/api/values/1 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------41184676334
Content-Length: 29278
-----------------------------41184676334
Content-Disposition: form-data; name="caption"
Summer vacation
-----------------------------41184676334
Content-Disposition: form-data; name="image1"; filename="GrandCanyon.jpg"
Content-Type: image/jpeg
(Binary data not shown)
-----------------------------41184676334--
Esta mensagem está dividida em duas partes, uma para cada controlo de formulário. Os limites das partes são indicados pelas linhas que começam com traços.
Observação
A fronteira da parte inclui um componente aleatório ("41184676334") para garantir que a cadeia de fronteira não apareça acidentalmente dentro de uma parte da mensagem.
Cada parte da mensagem contém um ou mais cabeçalhos, seguidos pelo conteúdo da peça.
- O cabeçalho Content-Disposition inclui o nome do controlo. Para ficheiros, também contém o nome do ficheiro.
- O cabeçalho Content-Type descreve os dados na peça. Se este cabeçalho for omitido, o padrão é texto/plano.
No exemplo anterior, o utilizador carregou um ficheiro chamado GrandCanyon.jpg, com tipo de conteúdo image/jpeg; e o valor da introdução de texto era "Férias de Verão".
Carregamento de Ficheiros
Agora vejamos um controlador Web API que lê ficheiros de uma mensagem MIME multiparte. O controlador lê os ficheiros de forma assíncrona. A Web API suporta ações assíncronas usando o modelo de programação baseada em tarefas. Primeiro, aqui está o código caso esteja a apontar para o .NET Framework 4.5, que suporta as palavras-chave async e await.
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
public class UploadController : ApiController
{
public async Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
}
Repara que a ação do comando não assume quaisquer parâmetros. Isto porque processamos o corpo do pedido dentro da ação, sem recorrer a um formatador do tipo media.
O método IsMultipartContent verifica se o pedido contém uma mensagem MIME multiparte. Se não, o controlador devolve o código de estado HTTP 415 (Tipo de Media Não Suportado).
A classe MultipartFormDataStreamProvider é um objeto auxiliar que aloca fluxos de ficheiros para ficheiros carregados. Para ler a mensagem MIME multiparte, chame o método ReadAsMultipartAsync . Este método extrai todas as partes da mensagem e escreve-as nos fluxos fornecidos pelo MultipartFormDataStreamProvider.
Quando o método termina, pode obter informações sobre os ficheiros a partir da propriedade FileData , que é uma coleção de objetos MultipartFileData .
- MultipartFileData.FileName é o nome local do ficheiro no servidor, onde o ficheiro foi guardado.
- MultipartFileData.Headers contém o cabeçalho da parte (não o cabeçalho do pedido). Pode usar isto para aceder aos cabeçalhos Content_Disposition e Content-Type.
Como o nome indica, o ReadAsMultipartAsync é um método assíncrono. Para realizar o trabalho após a conclusão do método, use uma tarefa de continuação (.NET 4.0) ou a palavra-chave await (.NET 4.5).
Aqui está a versão .NET Framework 4.0 do código anterior:
public Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data and return an async task.
var task = Request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
}
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
});
return task;
}
Leitura de Dados de Controlo de Formulários
O formulário HTML que mostrei anteriormente tinha controlo de entrada de texto.
<div>
<label for="caption">Image Caption</label>
<input name="caption" type="text" />
</div>
Pode obter o valor do controlo a partir da propriedade FormData do MultipartFormDataStreamProvider.
public async Task<HttpResponseMessage> PostFormData()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
await Request.Content.ReadAsMultipartAsync(provider);
// Show all the key-value pairs.
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
Trace.WriteLine(string.Format("{0}: {1}", key, val));
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
FormData é uma NameValueCollection que contém pares nome/valor para os controlos do formulário. A coleção pode conter chaves duplicadas. Considere este formulário:
<form name="trip_search" method="post" enctype="multipart/form-data" action="api/upload">
<div>
<input type="radio" name="trip" value="round-trip"/>
Round-Trip
</div>
<div>
<input type="radio" name="trip" value="one-way"/>
One-Way
</div>
<div>
<input type="checkbox" name="options" value="nonstop" />
Only show non-stop flights
</div>
<div>
<input type="checkbox" name="options" value="airports" />
Compare nearby airports
</div>
<div>
<input type="checkbox" name="options" value="dates" />
My travel dates are flexible
</div>
<div>
<label for="seat">Seating Preference</label>
<select name="seat">
<option value="aisle">Aisle</option>
<option value="window">Window</option>
<option value="center">Center</option>
<option value="none">No Preference</option>
</select>
</div>
</form>
O corpo do pedido pode ter este aspeto:
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="trip"
round-trip
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="options"
nonstop
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="options"
dates
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="seat"
window
-----------------------------7dc1d13623304d6--
Nesse caso, a coleção FormData conteria os seguintes pares chave/valor:
- Viagem: ida e volta
- Opções: sem parar
- Opções: Datas
- Assento: Janela