Sabemos que temos vários métodos suportados pelo protocolo HTTP, e entre eles, os mais comuns que vemos e utilizamos é o GET e o POST. Na primeira opção, o que enviamos ao servidor durante a requisição é o cabeçalho contendo tudo o que é necessário para efetuá-la, sem qualquer informação extra no corpo da mensagem. Ao contrário do que ocorre com o verbo POST, que além das informações que vão no cabeçalho, podemos também enviar qualquer conteúdo no corpo da mensagem.
Obviamente que o entendimento deste conteúdo por parte do serviço está condicionado ao header chamado Content-Type. Entre os vários tipos de conteúdo, temos os formatos Xml e Json. Mas ainda existe um outro formato, que também é muito comum em aplicações Web, que é conhecido como application/x-www-form-urlencoded. Este é o formato padrão que é utilizado pelo navegador, quando ele efetua um POST de um formulário HTML, codificando os campos existentes no mesmo, em uma coleção de pares de chave e valor.
Abaixo temos uma página HTML de exemplo, que contém um formulário simples, com dois campos do tipo Text e um botão que submete o formulário para o serviço que vamos criar mais tarde. Note que os controles estão envolvidos por um elemento chamado form, que contém o endereço (action) para onde o formulário será postado.
<form action=”http://localhost:1989/paginas/Enviar” method=”post”>
<input type=”text=” id=”Nome” name=”Nome” />
<input type=”text=” id=”Cpf” name=”Cpf” />
<input type=”submit” name=”Enviar” value=”Enviar” />
</form>
Ao preencher os campos e postar o formulário, podemos capturar a requisição e analisar o que está sendo enviado ao serviço mencionado. Podemos notar a estrutura da requisição abaixo, e que alguns headers menos relevantes para a situação, foram omitidos por questões de espaço. A nossa análise começa pelo header, onde temos o Content-Type definido como application/x-www-form-urlencoded, que como falamos acima, corresponde ao valor padrão para formulários HTML. No corpo da mensagem temos os campos do formulário separados pelo caracter &. Já os espaços são substituídos pelo caracter +. E, para finalizar, o nome do controle é definido como chave, enquanto o conteúdo do controle é definido como valor na coleção.
POST http://localhost:1989/paginas/Enviar HTTP/1.1
Accept-Language: en-US,pt-BR;q=0.5
Content-Type: application/x-www-form-urlencoded
Connection: Keep-Alive
Content-Length: 50
Host: localhost:1989
Nome=Israel+Aece&Cpf=111.222.333-44&Enviar=Enviar
O WCF Web API possibilita a leitura deste tipo de requisição, e para isso, temos um media type específico, chamado de FormUrlEncodedMediaTypeFormatter. Em um artigo anterior eu comentei sobre os media types, mais deixei este media type de fora justamente para abordá-lo aqui. Assim como os media types Xml e Json, este também é adicionado por padrão à execução, mapeando o tipo application/x-www-form-urlencoded como sendo o formato que ele interpreta.
Quando você requisita alguma informação para um serviço construído sobre essa nova API, a escolha do media type é realizada no interior da classe ObjectContent, através de um método protegido chamado SelectReadFormatter. Esse método percorre os formatadores que temos adicionados, verificando se o tipo de conteúdo da requisição é suportado por ele, e sendo, ele é imediatamente retornado para que o próprio ObjectContent possa ler o conteúdo utilizando-o. Com o formatador em mãos, a classe ObjectContent invoca o método ReadFromStream, exposto pela classe MediaTypeFormatter, repassando a ele o stream contendo o corpo da requisição, que fará o parser da mesma, e devolvendo para o serviço o objeto já deserializado. Abaixo temos um método simples, chamado Enviar, que recebe a requisição do formulário HTML que fizemos acima:
[WebInvoke]
public HttpResponseMessage Enviar(HttpRequestMessage request)
{
var conteudo = request.Content.ReadAsString();
return new HttpResponseMessage() { Content = new StringContent(conteudo) };
}
O grande problema do código acima, é que estamos lendo o corpo da mensagem como uma string, ficando difícil manipular e extrair informações do conteúdo enviado pelo cliente. Há algumas alternativas para conseguirmos extrair informações de uma forma mais amigável. A primeira delas é utilizando o método estático chamado ParseQueryString, da classe HttpUtility. Esse método recebe uma string e faz o parser dela, retornando o resultado (chaves e valores), em uma coleção especializada do tipo NameValueCollection. É importante lembrar que esta coleção é uma espécie de dicionário, suportando apenas strings como chave e como valor, e sua principal característica é não permitir chaves duplicadas, assim como qualquer dicionário, mas ela consegue agrupar valores que tenham a mesma chave, dentro de em um mesmo item. O código abaixo ilustra essa nova versão:
[WebInvoke]
public HttpResponseMessage Enviar(HttpRequestMessage request)
{
var conteudo = HttpUtility.ParseQueryString(request.Content.ReadAsString());
var resultado = new StringBuilder();
foreach (var item in conteudo.AllKeys)
resultado.AppendLine(string.Format(“{0}: {1}”, item, conteudo[item]));
return new HttpResponseMessage()
{
Content = new StringContent(resultado.ToString())
};
}
A outra opção que temos, é utilizando classes que correspondem ao formato Json. Há um método estático chamado ParseFormUrlEncoded, fornecido pela classe FormUrlEncodedExtensions, que dado uma string representando o corpo codificado no formato que estamos vendo neste artigo, ele retorna um objeto do tipo JsonObject, que nada mais é do que uma espécie de dicionário, onde a chave é do tipo string e o valor do tipo JsonValue. Estes objetos também fazem parte desta nova API, quais já discuti neste outro artigo com maiores detalhes.
[WebInvoke]
public HttpResponseMessage Enviar(HttpRequestMessage request)
{
var conteudoCodificado = request.Content.ReadAsString();
var conteudo = FormUrlEncodedExtensions.ParseFormUrlEncoded(conteudoCodificado);
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new ObjectContent<JsonValue>(conteudo);
response.Content.Headers.ContentType = new MediaTypeHeaderValue(“application/json”);
return response;
}
Depois de extrair a string contendo o corpo da requisição, submetemos a mesma para o método ParseFormUrlEncoded, que retornará o objeto JsonObject com todo o conteúdo já no formato Json. Depois disso, definimos o objeto construído como corpo da mensagem de resposta e, finalmente, definimos o tipo da resposta como sendo do tipo application/json. Abaixo temos a resposta sendo devolvida para o cliente, retornando conforme o esperado:
HTTP/1.1 200 OK
Content-Length: 63
Content-Type: application/json
{“Nome”:”Israel Aece”,”Cpf”:”111.222.333-44″,”Enviar”:”Enviar”}
Como vimos acima, o formato em que o formulário é encaminhado para o serviço, pode ser convertido implicitamente para Json, dando assim, um maior poder na manipulação da requisição e na geração dos resultados. Isso quer dizer que podemos também receber um conteúdo em formato application/x-www-form-urlencoded defindo no parâmetro do método, o tipo JsonObject ou invés de lidar diretamente com a classe HttpRequestMessage. Abaixo temos o método Enviar refletindo essa alteração:
[WebInvoke]
public HttpResponseMessage Enviar(JsonValue conteudo)
{
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new ObjectContent<JsonValue>(conteudo);
response.Content.Headers.ContentType = new MediaTypeHeaderValue(“application/json”);
return response;
}
Finalmente, podemos utilizar o método AsDinamyc da classe JsonValue, que converte o nosso objeto em um tipo dinâmico, que permite burlar a verificação estática dos membros, fazendo isso somente em tempo de execução. Abaixo temos esse exemplo, e logo na sequência, o resultado gerado por este método e que foi capturado. Note que o código fica muito mais simples e expressivo.
[WebInvoke]
public HttpResponseMessage Enviar(JsonValue conteudo)
{
dynamic valores = conteudo.AsDynamic();
return new HttpResponseMessage()
{
Content =
new StringContent(string.Format(“Nome: {0} – C.P.F.: {1}”, valores.Nome, valores.Cpf))
};
}
HTTP/1.1 200 OK
Content-Length: 46
Content-Type: text/plain; charset=iso-8859-1
Nome: “Israel Aece” – C.P.F.: “111.222.333-44”