Cutting Edge

Negociação de conteúdo e API Web para o desenvolvedor de ASP.NET MVC

Dino Esposito

Dino Esposito
Uma das coisas que mais gosto no ASP.NET MVC é a capacidade de expor uma fachada de métodos que podem ser facilmente invocados por clientes HTTP, incluindo páginas baseadas em jQuery, aplicativos móveis e back-ends C# simples. Durante muito tempo, a construção dessa camada de serviço ocorria na esfera dos serviços WCF (Windows Communication Foundation). Foram feitas tentativas para especializar o WCF para HTTP, como a introdução do mecanismo webHttpBinding e estruturas como o já aposentado REST Starter Kit. Nenhuma dessas abordagens, porém, poderia realmente eliminar os obstáculos para desenvolvedores, como excesso de configuração notório do WCF, uso excessivo de atributos e uma estrutura não desenvolvida especificamente para capacidade de teste. Depois surgiu a API Web, uma nova estrutura projetada para ser fina, testável, independente do ambiente de hospedagem (por exemplo, IIS) e focada em HTTP.

No entanto, a API Web tem uma interface de programação que parece quase demasiado semelhante ao ASP.NET MVC, na minha opinião. Esse não é um comentário negativo, porém, pois o ASP.NET MVC tem uma interface de programação limpa e bem definida. A API Web na verdade começou com um modelo de programação que se parecia com o WCF e depois passou a se parecer com o ASP.NET MVC.

Neste artigo, fornecerei uma visão geral da API Web da perspectiva do desenvolvedor ASP.NET MVC mediano e focarei em uma área funcional da API Web que representa uma vantagem em relação ao ASP.NET MVC simples: negociação de conteúdo.

Visão geral da API Web

API Web é uma estrutura que você pode usar para criar uma biblioteca de classes que possam lidar com solicitações HTTP. A biblioteca resultante, juntamente com algumas configurações iniciais, pode ser hospedada em um ambiente de tempo de execução e consumida pelos chamadores via HTTP. Os métodos públicos em classes de controlador tornam-se pontos de extremidade HTTP. As regras de roteamento configuráveis ajudam a definir o formato das URLs usadas para acessar métodos específicos. Com a exceção do roteamento, porém, grande parte do que define a forma padrão de manipulação de URL na API Web é convenção em vez de configuração.

Se você é desenvolvedor do ASP.NET MVC, neste momento você pode parar de ler e se perguntar por que diabos você iria querer usar uma nova estrutura que parece apenas duplicar o conceito de controladores que você "já tem" no ASP.NET MVC.

A resposta rápida para isso é, sim, provavelmente você não precisa da API Web no ASP.NET MVC, pois pode conseguir quase a mesma funcionalidade com controladores simples. Por exemplo, você pode facilmente retornar dados formatados como cadeias de caracteres JSON ou XML. Você pode facilmente retornar dados binários ou texto sem formatação. Você pode moldar os modelos de URL que você mais gosta.

A mesma classe de controlador pode fornecer dados JSON ou uma exibição em HTML, e você pode facilmente separar os controladores que retornam HTML dos controladores que só retornam dados. Na verdade, uma prática comum é ter uma classe ApiController no projeto onde você preenche todos os pontos de extremidade que devem retornar dados simples. Veja um exemplo:

public class ApiController : Controller {
public ActionResult Customers()
{
  var data = _repository.GetAllCustomers();
  return Json(data, JsonRequestBehavior.AllowGet);  }
  …
}

A API Web usa o melhor da arquitetura ASP.NET MVC e a melhora em duas áreas principais. Primeiro, ela introduz uma nova camada lógica conhecida como negociação de conteúdo com um conjunto padrão de regras para solicitação de dados em determinado formato, seja JSON, XML ou outro formato. Segundo, a API Web não depende de forma alguma do ASP.NET e do IIS – mais especificamente, não depende da biblioteca system.web.dll. Certamente ele pode ser hospedado em um aplicativo ASP.NET no IIS. No entanto, embora isso provavelmente continue a ser o cenário mais comum, uma biblioteca da API Web pode ser hospedada em qualquer outro aplicativo que forneça um ambiente de hospedagem ad hoc, como um serviço do Windows, um aplicativo WPF (Windows Presentation Foundation) ou um aplicativo de console.

Ao mesmo tempo, se você é um desenvolvedor especialista em ASP.NET MVC, os conceitos de controladores, associação de modelos, roteamento e filtros de ação da API Web soarão familiares para você.

Por que os desenvolvedores de formulários da Web adoram a API Web

Se você é desenvolvedor do ASP.NET MVC, pode se confundir inicialmente com os benefícios da API Web, porque seu modelo de programação parece quase idêntico ao ASP.NET MVC. No entanto, se você é desenvolvedor do Web Forms, não ficará confuso. Com a API Web, expor pontos de extremidade HTTP de dentro de um aplicativo do Web Forms é brincadeira de criança. Tudo que precisamos fazer é adicionar uma ou mais classes semelhantes a esta:

public class ValuesController : ApiController
{
  public IEnumerable<string> Get()
  {
    return new string[] { "value1", "value2" };
  }
  public string Get(int id)
  {
    return "value";
  }
}

Observe que este é o mesmo código que você usaria para adicionar um controlador da API Web a um aplicativo ASP.NET MVC. Você também deve especificar rotas. Este é um código que você quer executar na inicialização do aplicativo:

RouteTable.Routes.MapHttpRoute(
  name: "DefaultApi",
  routeTemplate: "api/{controller}/{id}",
  defaults: new { id = System.Web.Http.RouteParameter.Optional });

Salvo quando anotado em contrário com o atributo NonAction, os métodos públicos na classe que correspondem às convenções de nomenclatura e roteamento padrão são pontos de extremidade públicos de chamada HTTP. Eles podem ser chamados a partir de qualquer cliente sem a necessidade de classes proxy geradas, referências web.config ou código especial.

As convenções de roteamento na API Web ditam que a URL começa com /api seguido do nome do controlador. Observe que não há nome de ação claramente expresso. A ação é determinada pelo tipo de solicitação, seja GET, PUT, POST ou DELETE. Um nome de método que começa com Get, Put, Post ou Delete é convencionalmente mapeado para a ação correspondente. Por exemplo, um método GetTasks em um TaskController será invocado para qualquer solicitação GET para uma URL como /api/task.

Independentemente da aparente semelhança de comportamento e nomes de classe com o ASP.NET MVC, a API Web reside em um conjunto de assemblies completamente separado e usa um conjunto de tipos completamente diferente – System.Net.Http é o assembly principal.

Por dentro da negociação de conteúdo da API Web

A "negociação de conteúdo" é geralmente usada para descrever o processo de inspecionar a estrutura de uma solicitação HTTP recebida para descobrir os formatos em que o cliente pretende receber respostas. Tecnicamente, porém, a negociação de conteúdo é o processo em que cliente e servidor determinam o melhor formato de representação possível para usar em suas interações. Inspecionar a solicitação normalmente significa verificar alguns cabeçalhos HTTP, como Accept e Content-Type. Content-Type, em particular, é usado no servidor para processar solicitações POST e PUT e no cliente para escolher o formatador para respostas HTTP. Content-Type não é usado em solicitações GET.

O mecanismo interno de negociação de conteúdo, no entanto, é muito mais sofisticado. O cenário mencionado acima é o mais comum — devido às convenções e implementações padrão — mas não é o único possível.

O componente que governa o processo de negociação na API Web é a classe chamada DefaultContentNegotiator. Ela implementa uma interface pública (IContentNegotiator), para que você possa substituí-la por completo, se necessário. Internamente, o negociador padrão aplica vários critérios distintos para descobrir o formato ideal para a resposta.

O negociador trabalha com uma lista formatadores de tipo de mídia registrados — os componentes que realmente transformam objetos em um formato específico. O negociador verifica a lista de formatadores e para na primeira correspondência. Um formatador tem algumas maneiras de deixar o negociador saber que pode serializar a resposta para a solicitação atual.

A primeira verificação ocorre no conteúdo da coleção MediaTypeMappings, que é vazia por padrão em todos os formatadores de tipo de mídia predefinidos. Um mapeamento de tipo de mídia indica uma condição que, se verificada, dá direito ao formatador de serializar a resposta para a solicitação em andamento. Há alguns mapeamentos de tipo de mídia predefinidos. Um verifica determinado parâmetro na cadeia de caracteres de consulta. Por exemplo, você pode habilitar a serialização XML simplesmente exigindo que uma expressão xml=true seja adicionada à cadeia de caracteres de consulta usada para invocar a API Web. Para que isso aconteça, você precisa ter o seguinte código no construtor do seu formatador de tipo de mídia XML personalizado:

MediaTypeMappings.Add(new QueryStringMapping("xml", "true", "text/xml"));

De maneira semelhante, você pode fazer com que os chamadores expressem suas preferências adicionando uma extensão à URL ou adicionando um cabeçalho HTTP personalizado:

MediaTypeMappings.Add(new UriPathExtensionMapping("xml", "text/xml"));
MediaTypeMappings.Add(new RequestHeaderMapping("xml", "true",
  StringComparison.InvariantCultureIgnoreCase, false,"text/xml"));

Para a extensão do caminho da URL, significa que a seguinte URL será mapeada para o formatador XML:

http://server/api/news.xml

Observe que para que as extensões de caminho de URL funcionem, você precisa ter uma rota ad hoc, como:

config.Routes.MapHttpRoute(
  name: "Url extension",
  routeTemplate: "api/{controller}/{action}.{ext}/{id}",
  defaults: new { id = RouteParameter.Optional }
);

Para cabeçalhos HTTP personalizado, o construtor da classe RequestHeaderMapping aceita o nome do cabeçalho, seu valor esperado e alguns parâmetros extras. Um parâmetro opcional indica o modo de comparação de cadeias de caracteres desejado, e o outro é um booliano que indica se a comparação é em toda a cadeia. Se o negociador não conseguir encontrar uma correspondência no formatador usando as informações de mapeamento de tipo de mídia, ele verificará os cabeçalhos HTTP padrão, como Accept e Content-Type. Se nenhuma correspondência for encontrada, ele irá conferir a lista de formatadores novamente e verificar se o tipo de retorno da solicitação pode ser serializado por um dos formatadores.

Para adicionar um formatador personalizado, insira algo parecido com o seguinte código na inicialização do aplicativo (por exemplo, no método Application_Start):

config.Formatters.Add(xmlIndex, new NewsXmlFormatter());

Personalizando o processo de negociação

Na maioria das vezes, os mapeamentos de tipo de mídia permitem que você cumpra facilmente quaisquer requisitos especiais de serialização. No entanto, você sempre pode substituir o negociador de conteúdo padrão escrevendo uma classe derivada e substituindo o método MatchRequestMediaType:

protected override MediaTypeFormatterMatch MatchRequestMediaType(
  HttpRequestMessage request, MediaTypeFormatter formatter)
{
  ...
}

Você pode criar um negociador de conteúdo completamente personalizado com uma nova classe que implemente a interface IContentNegotiator. Depois de preparar um negociador manualmente, você o registra com o tempo de execução da API Web:

GlobalConfiguration.Configuration.Services.Replace(
  typeof(IContentNegotiator),
  new YourOwnNegotiator());

O código anterior normalmente vai no global.asax ou em um desses manipuladores de configuração úteis que o Visual Studio cria para você no modelo de projeto ASP.NET MVC Web API.

Controlando a formatação de conteúdo do cliente

O cenário mais comum para a negociação de conteúdo na API Web é quando o cabeçalho Accept é usado. Essa abordagem torna a formatação de conteúdo completamente transparente para o código da API Web. O chamador define o cabeçalho Accept de forma adequada (por exemplo, para text/xml) e a infraestrutura da API Web o manipula em conformidade. O código a seguir mostra como definir o cabeçalho Accept em uma chamada jQuery para um ponto de extremidade da API Web retornar algum XML:

$.ajax({
  url: "/api/news/all",
  type: "GET",
  headers: { Accept: "text/xml; charset=utf-8" }
});

No código C#, você define o cabeçalho Accept desta forma:

var client = new HttpClient();
client.Headers.Add("Accept", "text/xml; charset=utf-8");

Qualquer API HTTP em qualquer ambiente de programação permite definir cabeçalhos HTTP. E se você previr que pode ter chamadores em que isso possa ser um problema, a prática recomendada é adicionar também um mapeamento de tipo de mídia para que a URL contenha todas as informações necessárias sobre a formatação de conteúdo.

Tenha em mente que a resposta depende estritamente da estrutura da solicitação HTTP. Tente solicitar uma URL da API Web usando a barra de endereços do Internet Explorer 10 e do Chrome. Não se surpreenda se obtiver JSON em um caso e XML no outro. Os cabeçalhos Accept padrão podem ser diferentes em vários navegadores. Em geral, se a API for usada publicamente por terceiros, você deve ter um mecanismo baseado em URL para selecionar o formato de saída.

Cenários para usar a API Web

Em termos de arquitetura, a API Web é um grande avanço. Está se tornando cada vez mais importante com a recente interface Open Web para pacotes NuGet .NET (OWIN) (Microsoft.AspNet.Web­­Api.Owin) e o projeto Katana, que facilitam a hospedagem da API em aplicativos externos por um conjunto padrão de interfaces. Se você estiver criando soluções diferentes de aplicativos ASP.NET MVC, usar a API Web será fácil. Mas qual á a razão de usar a API Web em uma solução Web baseada em ASP.NET MVC?

Com o ASP.NET MVC simples, você pode construir facilmente uma fachada HTTP sem aprender coisas novas. Você pode negociar o conteúdo com bastante facilidade, com um pouco de código de uma classe base de controlador ou em qualquer método que precise dele (ou criando um ActionResult negociado). É tão fácil quanto ter um parâmetro extra na assinatura do método de ação, verificá-lo e depois serializar a resposta ao XML ou JSON de acordo. Essa solução é prática, desde que você se limite ao uso de XML ou JSON. Mas se você tiver mais formatos a serem levados em conta, provavelmente usará a API Web.

Como mencionado anteriormente, a API Web pode ser hospedada fora do IIS – por exemplo, em um serviço do Windows. Claramente, se a API reside em um aplicativo ASP.NET MVC, você fica vinculado ao IIS. O tipo de hospedagem, portanto, depende dos objetivos da camada de API que você está criando. Se ela for projetada para consumida apenas pelo site ASP.NET MVC envolvido, você provavelmente não precisará da API Web. Se sua camada de API criada for realmente um "serviço" para expor a API de um contexto de negócios, a API Web usada no ASP.NET MVC faz mais sentido.

Dino Esposito é o autor de “Architecting Mobile Solutions for the Enterprise” (Microsoft Press, 2012) e de “Programming ASP.NET MVC 5”, que será lançado em breve pela Microsoft Press. Divulgador técnico das plataformas .NET e Android no JetBrains e palestrante frequente em eventos do setor no mundo todo, Esposito compartilha sua visão de software em software2cents.wordpress.com e no Twitter, em twitter.com/despos.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Howard Dierking (Microsoft)
Howard Dierking é gerente de programa na equipe de estruturas e ferramentas do Windows Azure onde seu foco está em ASP.NET, NuGet e APIs Web. Anteriormente, Dierking trabalhou como editor-chefe da MSDN Magazine e também comandou o programa de certificação de desenvolvedores para o Microsoft Learning. Antes de fazer parte da Microsoft, ele foi desenvolvedor e arquiteto de aplicativos por dez anos, com foco em sistemas distribuídos.