Certificação Microsoft 70-487: Objetivo 4.1 – Design a Web API

Olá pessoal!

Hoje vamos começar o primeiro objetivo do capítulo sobre Web API, Design a Web API. Esse objetivo cobre como escolher um método HTTP apropriado, definir recursos HTTP com actions, usar rotas, escolher formatos e planejar quando usar actions assíncronas. Vamos lá?

Escolhendo Métodos HTTP

Resumindo a introdução, Web API é um serviço REST (Representational State Transfer) baseado em HTTP. Ao invés de chamar um método específico, como no WCF, com Web API você faz uma chamada para uma URL em conjunto com um método HTTP.

Segue uma lista com os métodos HTTP existentes e depois entraremos em detalhes sobre os mais importantes:

  • Delete: deleta a entidade;
  • Get: obtém os dados de uma ou mais entidades;
  • Put: atualiza a entidade inteira;
  • Patch: atualiza parcialmente a entidade;
  • Post: cria uma nova entidade;
  • Head: recupera somente os headers;
  • Options: requisita informações sobre as opções disponíveis de comunicação.

HttpGet

Geralmente o método mais usado, o HttpGet serve para obter dados de uma ou mais entidades. Ou seja, para cada entidade em um modelo, geralmente haverá um Get para obter todos os itens e outro Get para obter os detalhes de um item específico:

"/api/Foos" // obtém lista de todos os Foos
"/api/Foos/keyvalue" // obtém um Foo com a key especificada
"/api/Foos?attributename=attributevalue" // obtém um ou mais Foos com os atributos especificados

Caso nenhum valor seja encontrado, é importante lançar uma HttpResponseException com HttpStatusCode.NotFound (404) ao invés de simplesmente lançar uma Exception.

HttpDelete

Talvez o mais simples de todos, o HttpDelete simplesmente deleta uma entidade com o valor especificado, geralmente a chave. É aconselhável usar o prefixo Delete no nome do método do controller Web API.

Idealmente, há 3 possíveis retornos para um request Delete:

  • HttpStatusCode.OK (200), que indica sucesso;
  • HttpStatusCode.Accepted (202), que indica que o request foi processado e aceito mas está ainda pendente;
  • HttpStatusCode.NoResponse (204), valor padrão para métodos com retorno void

HttpPost

Para criar uma nova entidade, o método HttpPost deve ser usado. Assim como em Delete, o prefixo Post deve ser utilizado.

Em caso de sucesso, o retorno estipulado pelo protocolo HTTP 1.1 é HttpStatusCode.Created (201), mas o status code padrão é 200 (OK). É também aconselhável retornar a localização (URL) de detalhes da nova entidade criada. Seguindo esses padrões, a implementação de um HttpPost para criar uma Account seria assim:

[HttpPost]
public HttpResponseMessage PostAccount(Account account)
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, account);
string newUri = Url.Link("NamedApi", new { accountId = account.AccountId });
response.Headers.Location = new Uri(newUri);
return response;
}

HttpPut

HttpPut deve ser utilizado para operações upserts, que inserem novos registros ou atualizam registros existentes. Também devem utilizar o prefixo Put.

Para atualizar uma Account, por exemplo, você deve receber um Id para procurar e uma Account para atualizar os valores. A implementação seria assim:

[HttpPut]
public HttpResponseMessage PutAccount(int id, Account account)
{
// perform insert and/or edit here.
}

Definindo Recursos HTTP Com Actions

Um serviço Web API nada mais é do que uma aplicação que usa controllers e actions para mapear URLs com ações HTTP. Esta seção foca no básico de como construir uma aplicação Web API.

Criando O Model

Voltando um pouco ao capítulo anterior, em WCF era necessário decorar suas classes com os atributos DataContract e DataMember para trafegá-las pelos serviços.

Com Web API, isso não é mais necessário. O próprio framework toma conta de enviar sua classe via HTTP, serializada em JSON ou XML. Isso evita o acoplamento entre seu modelo e o serviço que o expõe, além de facilitar os possíveis clients desse serviço pelo fato de JSON e XML serem praticamente universais.

Para os exemplos a seguir, vamos considerar o seguinte model:

Criando O Controller

Os controllers são as classes que definem os serviços da sua Web API. Eles herdam de ApiController e cada método público vira uma action, como se fosse um método no WCF.

Veja a implementação dos controllers abaixo. Seguindo o padrão REST, é criado um serviço para cada tipo no modelo:

Com isso, você já tem sua Web API funcionando. Basta acessar http://localhost:{porta}/api/Account e a lista de Accounts é retornada. Chamando /api/Account?accountId=1, a Account com o Id 1 é retornada.

Um ponto importante é que, chamando essas URLs diretamente no browser, o comportamento é definido pelo próprio browser. O Chrome e o Firefox, por exemplo, mostrariam um XML na tela, enquanto o Internet Explorer faria o download de um JSON. Isso varia do request header Accept que o browser envia para a Web API.

Definindo Rotas

Toda a mágica da Web API de tornar métodos públicos em um controller acessíveis por clients acontece por causa da Routing Table.

Routing Table é um classe que implementa System.Web.Http.Routing.IHttpRoute e tem a tarefa de mepear um request para um controller e uma action específicos. No caso de Web API, a classe se chama WebApiConfig e fica dentro de App_Start:

Se você mudar o routeTemplate para "data/{controller}/{id}", por exemplo, você agora acessa sua api com http://localhost:{porta}/data/Account.

Quando uma requisição é feita para uma Web API, o framework tenta achar o controller e a action segundo o que está definido na Routing Table. Se não encontra, uma HttpStatusCode 404 é retornado. Caso contrário, o request é encaminhado para o método. As seguintes ações são tomadas pelo framework para encontrar o controller e a action:

  • Para achar o controller, a Web API adiciona a palavra Controller na variável do controller (por isso você chama diretamente Account, por exemplo);
  • Para a action, a Web API examina o método HTTP do request e tenta achar uma action com o nome correspondente;
  • Por padrão, isso só funciona com os métodos GET, POST, PUT e DELETE. Outro métodos são suportados, mas outro mecanismo é utilizado;
  • Por último, as variáveis no routeTemplate são mapeadas para os parâmetros da action (por padrão, id).

Você pode setar explicitamente um método HTTP em uma action. Isso é importante quando você quer que o nome do método seja diferente do método HTTP. Para fazer isso, basta decorar o método com o atributo System.Web.Http.ActionMethodSelectorAttribute. Ele possui 4 valores: HttpGet, HttpPost, HttpDelete e HttpPut. O método ficaria assim:

[HttpGet]
public Account GetAccount(int accountId)

Para definir múltiplos métodos HTTP para uma action ou suportar um método HTTP fora os 4 citados, você usa o atributo System.Web.Http.AcceptVerbsAttribute. O construtor aceita um array de strings que correspondem aos métodos que devem ser mapeados. O uso ficaria assim:

[System.Web.Http.AcceptVerbs("GET", "HEAD")]
public IEnumerable GetCustomers(int accountId)

Alterando Actions

Se você quiser criar as rotas com o nome da ation explícito, basta mudar a Routing Table para isso. O código ficaria assim:

Com isso, ao invés de acessar /api/Customer/1, você acessa /api/Customer/GetCustomers/1.

Você pode alterar o nome da action pelo atributo ActionNameAttribute:

[HttpGet]
[ActionName("FindCustomers")]
public IEnumerable SearchCustomers(string lastName)

Assim, você acessa essa action com a URL /api/Customer/FindCustomers?lastName=r.

O último ponto é que você pode querer que um método público no controller não seja uma action acessível pelo serviço. Para isso, decore o método com o atributo NonAction.

Escolhendo Formatos

O request header Accept indica o formato que o client espera como retorno. Acessando sua Web API pelo browser, geralmente o seguinte header é enviado:

Accept: text/html,application/xhtml+xml,application/xml

Por padrão, a Web API trabalha com JSON e XML. Isso é configurado por um objeto chamado media-type formatter.

Para essa seção, basta você saber isso e que você pode adicionar formatters customizados implementando a classe MediaTypeFormatter ou BufferedMediaTypeFormatter.

Actions Assíncronas

Ao fazer um request para um serviço Web API, uma thread é alocada do servidor para fazer o processamento necessário.
Em cenários de grandes processamentos de I/O (entrada/saída), como acesso a banco de dados e chamadas a outros serviços, é importante liberar a thread atual para que ela processe mais requests enquanto essa entrada/saída longa é executada.

O C# 5 nos deu mecanismos para fazer isso de forma muito simples: as palavras-chave async e await. Tome por exemplo a seguinte action de uma Web API:

public string Get()
{
WebClient webClient = new WebClient();
string result = webClient.DownloadString("http://microsoft.com");
return result;
}

Na maior parte do tempo, a thread alocada fica esperando o processamento I/O de fazer o request para o site da microsoft e buscar o HTML. Olhe essa action com async/await:

public async Task<string> Get()
{
WebClient webClient = new WebClient();
string result = await webClient.DownloadStringTaskAsync("http://microsoft.com");
return result;
}

Na chamada a await webClient.DownloadStringTaskAsync, a thread alocada é liberada para processar outros requests enquanto a requisição ao site da Microsoft é feita.

Um último alerta é que chamadas assíncronas devem ser feitas apenas para processamentos I/O. Se o método assíncrono fizer um processamento de CPU, outra thread terá que ser alocada para fazer esse processamento, o que invalida quaisquer benefícios da programação assíncrona (podendo até piorar devido à troca de threads).


Chegamos ao fim desse objetivo :)

Obrigado pela leitura e espere pelo próximo post sobre o objetivo 4.2, Implement a Web API.

Até mais!