O objetivo 4.2, Implement a Web API, é a continuação lógica do objetivo anterior com alguns tópicos mais avançados como content negotiation, HttpMessageHandler, injeção de dependência, Action Filters, streaming, etc. Vamos lá?
Manipulando Dados Em JSON
Com sua Web API retornando os dados, é interessante (e óbvio) que você construa uma interface para seus usuários trabalharem com os dados.
Ao criar uma aplicação Web API, a engine de views Razor é usada por padrão e uma view Index.cshtml é criada na pasta Views/Home, com algum layout pronto. Fazendo as alterações a seguir no HTML, a tela fica mais ou menos assim:
O plano é listar todos os Customers na ul customers e os que contêm o Last Name informado pelo usuário na ul results. Tudo isso será feito via AJAX (Asynchronous JavaScript and XML), onde o browser consegue fazer requisições ao serviço Web API sem a necessidade de carregar a página inteira novamente.
O seguinte script pode ser utilizado:
O ponto principal dessa seção é que você não precisa alterar nada no serviço Web API para que ele aceite formatos em JSON ou XML. O passo mais importante no client é apenas indicar explicitamente se ele está enviando JSON ou XML pelo request header Content-Type (o método do jQuery getJSON() fez isso).
Content Negotiation
Content Negotiation (negociação de conteúdo) é o processo onde o client indica o tipo do retorno que ele deseja ao fazer uma chamada a um serviço Web API.
Isso é feito por 4 headers no request:
- Accept: especifica o tipo do retorno do response. Para JSON o valor é application/json e para XML é application/xml ou text/xml;
- Accept-Charset: especifica o charset. Valores comuns são UTF-8 e ISO-8859-1;
- Accept-Encoding: indica quais encodings são aceitos;
- Accept-Language: especifica a linguagem preferida.
Para entender melhor, vamos olhar o request e o response da chamada ao serviço FindCustomers do exemplo anterior:
Request
GET http://localhost:53366/data/Customer/FindCustomers?lastName=r HTTP/1.1
X-Requested-With: XMLHttpRequest
Accept: application/json, text/javascript, */*; q=0.01
Referer: http://localhost:53366/
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: localhost:53366
DNT: 1
Connection: Keep-Alive
Response
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcUHJvamVjdHNcTXlTb2x1dGlvblxNeVdlYkFwaVxkYXRhXEN1c3RvbWVyXEZpbmRDdXN0b21lcnM=?=
X-Powered-By: ASP.NET
Date: Mon, 15 Jul 2013 15:52:20 GMT
Content-Length: 290
[{"CustomerId":4,"AccountId":2,"FirstName":"Captain","LastName":"America"},
{"CustomerId":8,"AccountId":3,"FirstName":"Ham","LastName":"Burgler"},
{"CustomerId":11,"AccountId":4,"FirstName":"Betty","LastName":"Rubble"},
{"CustomerId":12,"AccountId":4,"FirstName":"Barney","LastName":"Rubble"}]
Nesse caso, o client fez a requisição solicitando o resultado em JSON (application/json), JavaScript (text/javascript) ou qualquer outra coisa (*/*). O servidor informou que o retorno está em JSON pelo response header Content-Type.
Caso alguma Exception acontecesse, o response estaria com o status code 500 mas o body ainda seria JSON, provavelmente com a Exception serializada.
A execução dessa negociação acontece da seguinte forma: quando o request é recebido pelo servidor, o framework obtém uma instância de IContentNegotiator do HttpConfiguration, que tem a lista dos formatters disponíveis. O método Negotiate de IContentNegotiator é chamado com 3 parâmetros (o tipo do objeto sendo serializado, a lista dos formatters e o request) e retorna o formatter a ser utilizado e o media type para o response. Se nenhum formatter for encontrado, o client recebe um response com o HttpStatusCode NotAcceptable (406).
Data Binding
Um dos aspectos importantes da Web API é o binding dos dados. As actions que você cria recebem parâmetros e é importante entender como o framework “preenche” os valores.
No caso de tipos simples como int, string, bool, DateTime, etc., os valores vêm da URL. No nosso exemplo anterior, o serviço FindCustomers recebe uma string lastName que é passada na URL como query string: /data/Customer/FindCustomers?lastName=r
. Para tipo complexos (classes), o framework obtém os dados do body usando um formatter.
Você pode alterar o processo de binding padrão. O atributo FromUri, por exemplo, indica que um tipo complexo deve ser lido da URL ao invés do body:
public HttpResponseMessage Get([FromUri] Person person)
Dessa forma você chamaria esse serviço assim: /api/Persons/?FirstName=John&LastName=Doe
. De maneira similar, o atributo FromBody indica que o tipo simples deve ser obtido do body.
HttpMessageHandler
Message handlers fazem parte do pipeline do framework Web API e são responsáveis por interceptar as mensagens que chegam (requests) e as que são retornadas (responses) para os clients.
Você pode implementar um message handler para efetuar alguma lógica ou fazer alguma validação antes que uma action seja chamada. Isso é possível derivando a classe System.Net.Http.DelegatingHandler e implementando o método SendAsync. Por exemplo:
Para interromper o request, basta não chamar o método base e retornar um response de falha.
Seu handler precisa ainda ser registrado no pipeline da Web API. Você pode fazer isso de duas formas.
Na primeira, você adiciona o handler nos MessageHandlers do HttpConfiguration, no método Register da classe WebApiConfig: config.MessageHandlers.Add(new LoggingMessageHandler());
Dessa forma o handler é registrado globalmente, sendo executado para todos os requests.
O outro jeito é adicionar o handler para uma rota específica, fazendo ele ser executado apenas para actions daquela rota:
Implementando Injeção De Dependência
Por motivos fora do escopo do exame, dependências suck. Tome o código a seguir como exemplo:
public string Get()
{
WebClient webClient = new WebClient();
string result = webClient.DownloadString("http://microsoft.com");
return result;
}
Seria bem melhor você ter uma interface IContentRepository com um método GetContent(string uri) e ter uma implementação WebClientContentRepository, que utiliza a classe WebClient. O controller agora ficaria assim:
Com o código desacoplado, você precisa de um mecanismo para preencher a dependência do seu controller. A interface IDependencyResolver pode ser implementada da seguinte forma:
Configurando o DependencyResolver no objeto HttpConfiguration (config.DependencyResolver = new ResolveController();
), o framework já consegue resolver a dependência de IContentRepository.
Essa obviamente não é a melhor opção, pois cada nova dependência vai acarretar numa mudança na verificação do tipo do controller no método GetService. Para resolver esse problema, existem vários projetos open source que ajudam nessa implementação, e talvez uma delas caia no exame: Unity.
Ao instalar o Unity pelo NuGet, o arquivo Bootstrapper.cs é adicionado ao projeto. As únicas coisas que você precisa saber é que o método BootStrapper.Initialize deve ser chamado no Global.asax e que você registra uma dependência dessa forma: container.RegisterType
.
Action Filters E Exception Filters
Filters são métodos que podem alterar a action que está sendo executada.
Action Filter é um método que é invocado toda vez que uma action é chamada, enquanto Exception Filter é um método executado quando ocorre uma Exception não tratada.
Para implementar uma Action Filter você implementa a classe ActionFilterAttribute, e para um Exception Filter a classe ExceptionFilterAttribute.
Veja o seguinte exemplo:
Esse filtro pode ser aplicado para forçar a action a retornar XML, sobrescrevendo o comportamento padrão.
Você pode aplicar esse atributo a um controller (que vai aplicar para todas as actions) ou actions específicas. Também é possível configurar esse filtro globalmente pela propriedade HttpConfiguration.Filters.
Streaming Actions
A classe PushStreamContent pode ser usada para uma action retornar um stream, quando você precisa enviar uma quantidade muito grande de dados para um client.
Segue um exemplo de implementação. Esse código usa um timer para enviar alguns dados para os clients conectados a cada segundo. A classe PushStreamContent é inicializada com o método OnStreamAvailable:
Se você executar essa action, o request só vai terminar quando você fechar o browser. Dessa forma, você consegue enviar uma grande quantidade de dados em pacotes para o client.
E chegamos ao final desse objetivo 🙂
Obrigado pela leitura e fique de olho no post do próximo objetivo, Secure a Web API.
Até mais!