ASP.NET Web API - Estrutura da API

Israel Aece

Julho 2013

Apesar de serviços REST utilizar completamente o HTTP, é importante que tenhamos suporte para a construção e consumo destes tipos de serviços. Precisamos entender como estruturar, configurar e distribuir estes tipos de serviços.

Para facilitar tudo isso, a Microsoft preparou o ASP.NET para suportar o desenvolvimento de serviços REST. A finalidade deste capítulo é introduzir a template de projeto que temos, a API, a configuração mínima para a construção e exposição do mesmo.

A Template de Projeto

A construção de Web API está debaixo de um projeto ASP.NET MVC 4, e logo na sequência da escolha deste projeto, você deve escolher qual a template de projeto. Neste caso, temos que recorrer a opção chamada Web API, conforme vemos nas imagens abaixo. O projeto já está pré-configurado com o que precisamos para criar um serviço REST e expor para que seja consumido, sem a necessidade de realizar muitas configurações.

Dn376302.2B0905F53F93669A064F5ADEAF1E4678(pt-br,MSDN.10).png

Figura 1 - Escolha da Template do Projeto

O projeto já está configurado com as referências (DLLs) necessárias que contém os tipos e membros que utilizaremos na construção das Web APIs, sendo a principal delas o assembly System.Web.Http.dll. Dentro deste assembly temos vários namespaces com todos os elementos necessários que iremos utilizar para a construção de Web APIs. Além disso, já temos algumas configurações definidas para que seja possível criar e executar uma API criada, sem a necessidade de conhecer detalhes mais profundos em um primeiro momento.

Analisando os itens do Solution Explorer, vemos que além dos arquivos tradicionais de um projeto ASP.NET (como o Global.asax e o Web.config), vemos ali um arquivo chamado WebApiConfig.cs, que é uma classe que contém a configuração padrão do roteamento (que será abordado mais adiante) para o funcionamento dos serviços REST. Além disso, temos um serviço já criado como exemplo, que está contido na classe/arquivo ValuesController.cs.

Dn376302.033AB50B73368C87277754193245B13A(pt-br,MSDN.10).png

Figura 2 - Elementos Padrão de um Projeto Web API

A classe ApiController

A construção de Web APIs utilizando o ASP.NET segue uma certa simetria em relação a construção de um site baseado no padrão MVC. Para a construção de views o MVC exige que se tenha um controlador (controller) para receber, processar e retornar as requisições que são realizadas para o site.

De forma parecida trabalha a Web API. Todas as Web APIs construídas no ASP.NET devem herdar de uma classe abstrata chamada ApiController. Esta classe fornece toda a infraestrutura necessária para o desenvolvimento destes tipos de serviços, e entre as suas tarefas, temos: fazer a escolha do método a ser executado, conversão das mensagens em parâmetros, aplicação de eventuais filtros (de vários níveis), etc. Cada requisição, por padrão, terá como alvo um método dentro desta classe, que será responsável por processar a mesma e retornar o resultado.

A criação de uma Web API pode ser realizada de forma manual herdando da classe ApiController, ou se preferir, pode ser utilizado o assistente que o próprio Visual Studio disponibiliza, onde já existe algumas opções predefinidas para que a classe já seja criada com a estrutura básica para alguns cenários.

Dn376302.7950ACC23DC1259FDFFABA6FE38DC754(pt-br,MSDN.10).png

Figura 3 -Opções para a criação do Controller

Independentemente da forma que você utilize para a criação do controlador, sempre teremos uma classe que servirá como a base para a API, e o código abaixo ilustra a herança da classe ApiController:

    using System.Web.Http;
    namespace MusicStore.Controllers
    {
        public class ArtistasController : ApiController
        {
        }
    }

Uma consideração importante é com relação ao sufixo Controller que o nome da classe que representará a API deverá ter. Apesar dele ser transparente para o cliente, isso é utilizado pelo ASP.NET para encontrar o controller durante a requisição. Podemos notar também que a classe não possui nenhum método. Para que ela comece a ter sentido, precisamos criar os métodos que atenderão as requisições que serão feitas para este serviço. Da mesma forma que fazemos no MVC, aqui criaremos as ações que retornarão dados aos clientes, formatados em algum padrão e, opcionalmente, os clientes poderão parametrizar a requisição, caso o serviço permita isso.

A classe pode ter quantos métodos forem necessários, apenas temos que ter um pouco do bom senso aqui para não darmos mais funcionalidade do que deveria para a mesma, considerando a granularidade. A criação dos métodos possuem algumas convenções que se seguidas corretamente, não haverá maiores configurações a serem realizadas para que ele já esteja acessível ao rodar o serviço.

Mas como que o ASP.NET escolhe qual dos métodos acessar? O HTTP possui o que chamamos de verbos (algumas vezes chamados de métodos), e os mais comuns são: GET, POST, PUT e DELETE, e cada um deles indica uma determinada ação a ser executada em um recurso específico.

  • GET: Está requisitando ao serviço um determinado recurso, apenas isso. Este verbo deve apenas extrair a informação, não alterando-a.
  • POST: Indica ao serviço que a ele deve acatar o recurso que está sendo postado para o mesmo, e que muito vezes, o adicionamos em algum repositório.
  • PUT: Indica que ao serviço que o recurso que está sendo colocado deve ser alterado se ele já existir, ou ainda, pode ser adicionado caso ele ainda não exista.
  • DELETE: Indica que o serviço deve excluir o recurso.

Mas o que isso tem a ver com o Web API? O ASP.NET já mapeia todos estes conhecidos verbos do HTTP para métodos que estejam criados o interior do controller. Para que isso aconteça, precisamos definir os métodos com o nome de cada verbo acima descrito, e com isso, automaticamente, quando o ASP.NET recepcionar a requisição, esse será o primeiro critério de busca aos métodos.

public class ArtistasController : ApiController
{
    public Artista Get(int id)
    {
    }

    public void Post(Artista novoArtista)
    {
    }
}

É claro que não estamos condicionados a trabalhar desta forma. Se você quer criar uma API em português, talvez utilizar a configuração padrão não seja a melhor opção pela coerência. Podemos nomear os métodos da forma que desejarmos, mas isso nos obrigará a realizar algumas configurações extras, para direcionar o ASP.NET a como encontrar o método dentro da classe, pois agora, difere daquilo que foi previamente configurado.

Para realizar essa configuração, vamos recorrer à alguns atributos que já existem dentro do ASP.NET e que foram construídos para cada um dos verbos do HTTP: HttpGetAttribute, HttpPutAttribute, HttpPostAttribute, HttpDeleteAttribute, etc. Quando um destes atributos é colocado em um método, ele permitirá que ele seja acessível através daquele verbo. Utilizando o mesmo exemplo anterior, se alterarmos o nome dos métodos apenas, eles deixarão de estar acessíveis aos clientes.

public class ArtistasController : ApiController
{
    [HttpGet]
    public Artista Recuperar(int id)
    {
    }

    [HttpPost]
    public void Adicionar(Artista novoArtista)
    {
    }
}

E ainda, como alternativa, podemos recorrer ao atributo ActionNameAttribute para alterar o nome que será publicado em relação aquele que definido no método, dando a chance de utilizar uma convenção de nomenclatura para escrita e outra para publicação.

public class ArtistasController : ApiController
{
    [ActionName(“Recuperar”)]
    public Artista Get(int id)
    {
    }
}

Como comentado acima, existe um atributo para cada verbo. Para uma maior flexibilidade, temos também o atributo AcceptVerbsAttribute, que nos permite informar em seu construtor quais os verbos que podem chegar até o método em questão.

public class ArtistasController : ApiController
{
    [AcceptVerbs("POST", "PUT")]
    public void Adicionar(Artista novoArtista)
    {
    }
}

E se houver um método público que se encaixe com as regras dos verbos que foram comentadas acima e não queremos que ele esteja disponível publicamente, podemos proibir o acesso decorando o método com o atributo NonActionAttribute.

Parametrização dos Métodos

Todos os métodos podem receber informações como parâmetros, e como saída, podemos retornar alguma informação que caracteriza o sucesso ou a falha referente a execução do mesmo. Quando falamos de métodos que são disponibilizados para acesso remoto, isso não é diferente.

Os métodos podem necessitar alguns parâmetros para executar a tarefa que está sendo solicitada. Os parâmetros podem ter tipos mais simples (como inteiro, string, etc.) até objetos mais complexos (Usuario, Pedido, Produto, etc.). A utilização de objetos complexo nos permite descrever o nosso negócio, tornando-o bem mais intuitivo que criar um método contendo uma infinidade de parâmetros.

Dn376302.33D25C6F5BFF508FB570A7CEB0DB91C1(pt-br,MSDN.10).png

Figura 4 - Postando um recurso de um objeto complexo.

Apesar de estarmos habituados a declarar o método, seus parâmetros e resultado da forma tradicional quando estamos construindo uma Web API, eles são trafegados entre o cliente e o serviço utilizando o modelo do HTTP, ou seja, eles poderão ser carregados através de querystrings, headers ou do corpo das mensagens.

De forma semelhante ao que ocorre no ASP.NET MVC, o ASP.NET Web API é capaz de mapear as querystrings (que são tipos simples (textos, números, etc.)) para os parâmetros dos métodos do controller de forma automática. Já os objetos complexos viajam entre o cliente e o serviço (e vice-versa) no corpo da mensagem. Toda a mágica da transformação da mensagem em um objeto customizado é realizada pelos formatadores de conteúdo, qual terá um capítulo específico para abordar este assunto.

Quando estamos acessando os métodos através do verbo GET, as querystrings são mapeadas para os parâmetros destes métodos; já quando realizamos o POST, o corpo da mensagem é transformado no objeto complexo, conforme vimos na imagem acima. Existe algumas opções mais rebuscadas se quisermos customizar como estes parâmetros são lidos das mensagens, e para isso, podemos recurrer aos atributos FromBodyAttribute ou ao FromUriAttribute. Estes atributos nos permite direcionar o ASP.NET a buscar os valores para abastecer os parâmetros em locais diferentes dos padrões.

Podemos abastecer os parâmetros baseados nas querystrings ou no corpo da mensagem. O corpo da mensagem pode estar em diversos formatos (abordaremos com mais detalhes adiante), e um deles é a postagem sendo realizada através de um formulário HTML.

<form action="https://localhost:43139/api/Artistas/Adicionar" method="post">
  <input type="text=" id="Id" name="Id" />
  <input type="text=" id="Nome" name="Nome" />
  <input type="text=" id="Email" name="Email" />
  <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. O que chama atenção na requisição abaixo é o header Content-Type definido como application/x-www-form-urlencoded, que 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 no dicionário.

    POST https://localhost:43139/api/artistas/adicionar HTTP/1.1
    User-Agent: Fiddler
    Host: localhost:43139
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 41
    Id=12&Nome=Israel&Email=ia@israelaece.com

Ao postar um formulário para um método que possui um objeto, o ASP.NET Web API já é capaz de extrair as informações do corpo da mensagem (a partir do dicionário de valores) e abastecer cada propriedade deste objeto. Se desejar, podemos ler individualmente as informações que estão sendo encaminhadas, não acionando o formatador de conteúdo, utilizando um outro tipo de codificação quando estamos postando o formulário.

No exemplo acima, o formulário foi postado utilizando o formato application/x-www-form-urlencoded, que é mais ou menos os valores do formulário codificados como se fossem itens de uma querystring. Existe um outro formato que é o multipart/form-data, que possui uma codificação mais sofisticada. Em geral, ele é utilizado em conjunto com o elemento do tipo file, que é quando queremos fazer upload de arquivos para o servidor.

Quando postamos um arquivo, automaticamente o tipo é definido como sendo multipart/form-data e na sequencia, vemos o arquivo anexado.

    POST https://localhost:43139/api/artistas/AlterarFoto HTTP/1.1
    Content-Type: multipart/form-data; boundary=-------------------------acebdf13572468
    User-Agent: Fiddler
    Host: localhost:43139
    Content-Length: 1168
    
    ---------------------------acebdf13572468
    Content-Disposition: form-data; name="fieldNameHere"; filename="MaxPezzali.png"
    Content-Type: image/png
    
    ---- REMOVIDO POR QUESTÕES DE ESPAÇO ---

Mas para recepcionar este tipo de requisição temos que preparar o serviço. Note que ele não recebe nenhum parâmetro; ele é extraído do corpo da mensagem ao executar o método ReadAsMultipartAsync, que assincronamente lê e “materializa” os arquivos, salvando automaticamente no caminho informado no provider. Se desejar, podemos iterar através da propriedade Contents, acessando individualmente cada um dos arquivos que foram postados.

[HttpPost]
public async Task<HttpResponseMessage> AlterarFoto()
{
    var provider = 
        new MultipartFormDataStreamProvider(
            HttpContext.Current.Server.MapPath("~/Uploads"));

    return await Request
        .Content
        .ReadAsMultipartAsync(provider)
        .ContinueWith<HttpResponseMessage>(t =>
        {
            if (t.IsFaulted || t.IsCanceled)
                return Request.CreateErrorResponse(
                    HttpStatusCode.InternalServerError, t.Exception);

            return Request.CreateResponse(HttpStatusCode.OK);
        });
}

Apesar das técnicas acima serem interessantes, utilizar uma delas pode ser um problema ao trabalhar com serviços REST, devido ao fato de que em algumas situações haver a necessidade de ter o controle total das mensagens HTTP.

Com o intuito de facilitar e dar mais controle para ao desenvolvedor, a Microsoft inclui nesta API classes que representam a mensagem de requisição (HttpRequestMessage) e de resposta (HttpResponseMessage). Cada uma dessas classes trazem várias propriedades, onde cada uma delas expõe características do protocolo HTTP, tais como: Content, Headers, Method, Uri, StatusCode, etc.

O serviço passará a utilizar essas classes em seus métodos, ou seja, receberá um parâmetro do tipo HttpRequestMessage, que possui todas as informações necessárias solicitadas pelo cliente, enquanto o retorno será do tipo HttpResponseMessage, que será onde colocaremos todas as informações de resposta para o mesmo cliente, sendo essas informações o resultado em si, o código de status do HTTP, eventuais headers, etc.

public class ArtistasController : ApiController
{
    [HttpGet]
    public HttpResponseMessage Ping(HttpRequestMessage info)
    {
        return new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("ping...")
        };
    }
}

O corpo da mensagem de retorno também pode ser customizado, onde podemos retornar uma simples string ou até mesmo um objeto complexo. Isso tudo será abordado com maiores detalhes nos capítulos seguintes.

Métodos Assíncronos

A Microsoft incorporou diretamente no C# e no VB.NET o suporte para programação assíncrona. A ideia é facilitar a programação assíncrona, que não era nada trivial até o momento, tornando a escrita de um código assíncrono muito próximo a escrita de um código síncrono, e nos bastidores, o compilador faz grande parte do trabalho. Grande parte das funcionalidades do .NET Framework que já possuem suporte nativo ao consumo em formato assíncrono, foram readaptados para que assim, os desenvolvedores possam fazer uso dos novos recursos oferecidos pela linguagem para consumi-los.

Com o ASP.NET Web API também podemos fazer com que os métodos expostos pela API sejam processados assincronamente, usufruindo de todos os benefícios de um método ser executado de forma assíncrona.

Vamos supor que o nosso serviço de Artistas deve recorrer à um segundo serviço para extrair as notícias referentes à um determinado artista. No interior do método que retorna o artista, faremos a chamada para o serviço de Notícias e esperamos pelo resultado, que ao voltar, efetuamos o parser e, finalmente, convertemos para o formato esperado e retornamos ao cliente.

Ao executar este tipo de serviço, a requisição será bloqueada pelo runtime até que o resultado seja devolvido para o serviço. Isso prejudica, e muito, a escalabilidade do serviço. O fato da thread ficar bloqueada enquanto espera pelas notícias, ela poderia estar atendendo à outras requisições, que talvez não exijam recursos de terceiros (I/O bound). O fato de disponibilizar a thread para que ela possa atender à outras requisições, farão com que elas não esperem por um tempo indeterminado, pois como dependemos do resultado de um terceiro, poderíamos arranjar muito trabalho para esta thread, até que ela precise retomar o trabalho da requisição anterior.

Para implementar o controller da API de forma assíncrona, exigirá algumas mudanças, mas nada que faça com que seja necessário escrever e/ou gerenciar uma porção de código para garantir o assincronismo (IAsyncResult por exemplo). Com isso, o primeiro detalhe a notar na escrita da ação assíncrona, é a exigência da keyword async, que faz do C#.

public class ArtistasController : ApiController
{
    [HttpGet]
    public async Task<Artista> Recuperar(int id)
    {
        var artista = this.repositorio.Buscar(id);

        using (var client = new HttpClient())
            artista.Noticias =
                await
                    (await client.GetAsync(ServicoDeNoticias))
                    .Content
                    .ReadAsAsync<IEnumerable<Noticia>>();

        return artista;
    }
}

Tratamento de Erros

Durante a execução, uma porção de exceções podem acontecer, sejam elas referentes à infraestrutura ou até mesmo à alguma regra de negócio, e o não tratamento correto delas, fará com as mesmas não sejam propagadas corretamente ao cliente que consome a API. A maior preocupação aqui é mapear o problema ocorrido para algum código HTTP correspondente.

Isso se faz necessário porque exceções são características de plataforma, e precisamos de alguma forma expressar isso através de algum elemento do HTTP, para que cada um dos – mais variados – clientes possam interpretar de uma forma específica. Por padrão, todos os erros que ocorrem no serviço e não tratados, retornando ao cliente o código de status 500, que indica um erro interno do serviço (Internal Server Error).

O ASP.NET Web API possui uma exceção chamada HttpResponseException, que quando é instanciada definimos em seu construtor o código de status do HTTP indicando o erro que ocorreu. Para exemplificar o uso deste tipo, podemos disparar o erro 404 (Not Found) se o cliente está solicitando um artista que não existe.

public class ArtistasController : ApiController
{
    [HttpGet]
    public Artista Recuperar(int id)
    {
        var artista = this.repositorio.Buscar(id);

        if (artista == null)
            throw new HttpResponseException(
                new HttpResponseMessage(HttpStatusCode.NotFound)
                {
                    Content = new StringContent("Artista não encontrado"),
                    ReasonPhrase = "Id Inválido"
                });

        return artista;
    }
}

Ainda como opção temos a classe HttpError para expressar o problema que ocorreu dentro do método. A principal vantagem de utilizar esta classe em relação aquele que vimos acima, é que o conteúdo que identifica o erro é serializado no corpo da mensagem, seguindo as regras de negociação de conteúdo.

public class ArtistasController : ApiController
{
    [HttpGet]
    public HttpResponseMessage Recuperar(int id)
    {
        var artista = this.repositorio.Buscar(id);

        if (artista == null)
            return Request.CreateErrorResponse(
                HttpStatusCode.NotFound, 
                new HttpError("Artista não encontrado"));
        else
            return Request.CreateResponse(HttpStatusCode.OK, artista);
    }
}

Tratar as exceções in-place (como acima) pode não ser uma saída elegante, devido a redundância de código. Para facilitar, podemos centralizar o tratamento em nível de aplicação, o que permitirá com que qualquer exceção não tratada no interior da ação, será capturada por este tratador, que por sua vez analisará o erro ocorrido, podendo efetuar algum tipo de logging e, finalmente, encaminhar o problema ao cliente. É neste momento que podemos efetuar alguma espécie de tradução, para tornar a resposta coerente ao que determina os códigos do HTTP, como por exemplo: se algum erro relacionado à autorização, devemos definir como resposta 403 (Forbidden); já se algum informação está faltante (assim como vimos no exemplo acima), devemos retornar o status 400 (Bad Request); já se o registro procurado não foi encontrado, ele deverá receber o código 404 (Not Found), e assim por diante.

Para isso vamos recorrer a criação de um filtro customizado para centralizar a tradução de algum problema que acontecer. Só que neste caso, temos uma classe abstrata chamada de ExceptionFilterAttribute, que já fornece parte da infraestrutura necessária para o tratamento de erros que ocorrem, e é equivalente ao atributo HandleErrorAttribute que temos no ASP.NET MVC. Tudo o que precisamos fazer aqui é sobrescrever o método OnException e definir toda a regra de tradução necessária. Abaixo um exemplo simples de como proceder para realizar esta customização:

public class ExceptionTranslatorAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext ctx)
    {
        var errorDetails = new ErrorDetails();
        var statusCode = HttpStatusCode.InternalServerError;

        if (ctx.Exception is HttpException)
        {
            var httpEx = (HttpException)ctx.Exception;
            errorDetails.Message = httpEx.Message;
            statusCode = (HttpStatusCode)httpEx.GetHttpCode();
        }
        else
        {
            errorDetails.Message = "** Internal Server Error **";
        }

        ctx.Result =
            new HttpResponseMessage<ErrorDetails>(errorDetails, statusCode);
    }
}

Não vamos se aprofundar muito aqui, mas a criação de filtros é um ponto de estensibilidade, e que teremos um capítulo específico para abordar este assunto. O que precisamos nos atentar aqui é existem vários escopos para registros um filtro deste tipo, podendo ele ser aplicado em nível de ação, de controller ou globalmente.

O código abaixo registra o tradutor de exceções em nível global, recorrendo ao arquivo Global.asax para isso, que será utilizado por todo e qualquer API que estiver abaixo deste projeto. Maiores detalhes sobre a configuração de serviço, serão abordados em breve, ainda neste capítulo.

    GlobalConfiguration
        .Configuration
        .Filters
        .Add(new ExceptionTranslatorAttribute());

Validações

Como vimos anteriormente, o ASP.NET Web API é capaz de construir um objeto complexo baseado no corpo da requisição. Mas como acontece em qualquer aplicação, pode ser que o objeto esteja com um valores inválidos, o que impedirá do mesmo ser processado corretamente.

Mesma se tratando de serviços onde o foco é a integração entre aplicações, é necessário que se faça a validação, para garantir que a requisição tenha as informações corretas para ser processada.

Uma das opções que temos para isso, é fazer como já realizamos no ASP.NET MVC: recorrer aos Data Annotations do .NET Framework para validar se o objeto encontra-se em um estado válido. Para isso, basta decorarmos as propriedades com os mais variados atributos que existem debaixo do namespace (e assembly) System.ComponentModel.DataAnnotations.

Para iniciar devemos referenciar em nosso projeto o assembly que contém os tipos para a validação: System.ComponentModel.DataAnnotations.dll. A partir do momento em que referenciamos este assembly na aplicação, podemos aplicar em nossos objetos os atributos que determinam as regras que cada propriedade deverá respeitar. No exemplo abaixo ilustra estamos informando ao ASP.NET que a propriedade Nome deve ser preenchida e a propriedade e-mail deve representar um endereço de e-mail em formato válido.

public class Artista
{
    public int Id { get; set; }

    [Required]
    public string Nome { get; set; }

    [EmailAddress]
    public string Email { get; set; }
}

Só que os atributos não funcionam por si só. O ASP.NET Web API já está preparado para realizar a validação do objeto que está sendo postado, avaliando se cada propriedade do mesmo está seguindo os atributos aplicados à ela.

Como o ASP.NET já realiza toda a validação, o que nos resta no interior do método é verificar se o objeto está válido. A classe ApiController fornece uma propriedade chamada ModelState. Essa propriedade dá ao controller a possibilidade de verificar se o modelo que está sendo postado está ou não valido através da propriedade boleana IsValid, e além disso, nos permite acessar as propriedades problemáticas, ou melhor, aquelas que não passaram na validação, que está baseada nos atributos que foram aplicados.

Abaixo estamos avaliando essa propriedade, e se houver alguma informação errada, retornamos ao cliente o código 400 (Bad Request), indicando que há problemas na requisição que foi encaminhada para o serviço. O que é importante notar é que o método que recebe e trata a requisição não possui regras de validação para o objeto. Isso acabou sendo terceirizado para os Data Annotations do .NET, que acabam realizando toda a validação em estágios anteriores ao processamento da requisição, e com isso o método fica apenas com a responsabilidade de processar se a requisição foi encaminhada da forma correta.

public class ArtistasController : ApiController
{
    [HttpPost]
    public HttpResponseMessage Adicionar(Artista artista)
    {
        if (ModelState.IsValid)
        {
            //...
            return new HttpResponseMessage(HttpStatusCode.Created);
        }

        return new HttpResponseMessage(HttpStatusCode.BadRequest);
    }
}

Configuração

Ao trabalhar com algum tipo de projeto .NET, estamos acostumados a lidar com a configuração e estensibilidade do mesmo utilizando os arquivos de configuração (App.config ou Web.config). O ASP.NET Web API trouxe toda a configuração para ser realizada através do modelo imperativo (via código) ao invés do modelo declarativo (via Xml).

Para centralizar toda a configuração das APIs, temos a disposição um objeto global que possui uma variedade de métodos para registrarmos todos os elementos que podemos customizar. Toda essa configuração é realizada a partir do método Application_Start do arquivo Global.asax. Como a template do projeto segue a mesma linha do ASP.NET MVC, a configuração das APIs são realizadas em uma classe estática chamada WebApiConfig, que recebe como parâmetro um objeto do tipo HttpConfiguration, acessível através da propriedade estática Configuration da classe GlobalConfiguration.

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        WebApiConfig.Register(GlobalConfiguration.Configuration);
    }
}

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

Inicialmente o configurador das APIs possui apenas um código que registra a rota padrão para que as requisições sejam encaminhadas corretamente para os métodos. Não vamos nos estender aqui, pois há um capítulo dedicado a este assunto. A classe HttpConfiguration possui uma série de propriedades que serão exploradas ao longo dos próximos capítulos.

Elementos do HTTP

Além das informações que são enviadas e recebidas através do corpo da mensagem, podemos recorrer à alguns elementos inerentes ao HTTP, para incluir informações que fazem parte da requisição, como por exemplo, ID para controle da segurança, tags para caching, etc.

Para essas informações, podemos recorrer aos famosos elementos do HTTP que são a coleção de headers, de querystrings e cookies. Como falamos anteriormente, se quisermos ter o controle da mensagem, o método/ação deve lidar diretamente com as classes HttpRequestMessage e HttpResponseMessage. Esses objetos expõem propriedades para acesso à estes recursos, e métodos de estensão facilitam o acesso a estas informações.

O cookie nada mais é que um header dentro da requisição, que quando o serviço adiciona um cookie, chega uma “instrução” (header) ao cliente chamado Set-Cookie, que faz com que o mesmo seja salvo e adicionado nas futuras requisições. No ASP.NET Web API temos uma classe chamada CookieHeaderValue, que representa um cookie, que depois de criado, podemos adicionar na coleção de cookies da resposta e entregar ao cliente.

[HttpGet]
public HttpResponseMessage Ping(HttpRequestMessage request)
{
    var response = new HttpResponseMessage(HttpStatusCode.OK);
    var id = request.Headers.GetCookies("Id").FirstOrDefault();

    if (id == null)
    {
        response.Headers.AddCookies(new CookieHeaderValue[]
        {
            new CookieHeaderValue("Id", Guid.NewGuid().ToString())
            { 
                Expires = DateTimeOffset.Now.AddDays(10)
            }
        });
    }

    return response;
}

Dn376302.1AD12F65D558E810AAFC7833698E0CB2(pt-br,MSDN.10).png

Ao realizar a requisição, temos o header Set-Cookie sendo devolvido, que competirá ao cliente que está acessando, salvá-lo para enviar em futuras requisições. Se fizermos isso com o Fiddler, podemos perceber que quando a ação é executada, o header Cookie é interpretado e entregue ao ASP.NET Web API como uma instância da casse CookieHeaderValue.

Dn376302.84F6C98A851CD898F983E890376C4163(pt-br,MSDN.10).png

Figura 6 - Cookie sendo retornado para o serviço.

No exemplo acima apenas estamos lidando com um único valor, mas nada impede de termos uma informação mais estruturada dentro do cookie, ou seja, podemos armazenar diversos dados. A classe CookieHeaderValue possui um construtor que permite informar uma coleção do tipo NameValueCollection, que define uma lista de valores nomeados, onde podemos adicionar todos elementos que queremos que sejam levado ao cliente, e que serão separados pelo caracter “&”.

[HttpGet]
public HttpResponseMessage Ping(HttpRequestMessage request)
{
    var response = new HttpResponseMessage(HttpStatusCode.OK);
    var info = request.Headers.GetCookies("Info").FirstOrDefault();

    if (info == null)
    {
        var data = new NameValueCollection();
        data["Id"] = Guid.NewGuid().ToString();
        data["Type"] = "Simplex";
        data["Server"] = "SRV01";

        response.Headers.AddCookies(new CookieHeaderValue[]
        {
            new CookieHeaderValue("Info", data)
        });
    }

    return response;
}

Dn376302.F250C44E15D083E10DE7D480CD64E5A9(pt-br,MSDN.10).png

Figura 7 - Cookie com múltiplos valores.

Documentação

Quando estamos falando de serviços baseados em SOAP, temos um documento comando de WSDL (Web Service Description Language) baseado em XML, que descreve todas as características (definições) de um determinado serviço. É justamente esse documento que é utilizado por ferramentas como o svcutil.exe e a opção "Add Service Reference" do Visual Studio, para gerar uma classe que representará o proxy, que por sua vez, será utilizado pelos clientes para consumir o serviço como se fosse uma classe local, mas durante a execução, a mensagem será enviada ao ponto remoto.

Mas é importante dizer que mesmo serviços baseados em REST, também precisam, de alguma forma, expor alguma espécie de documentação, para descrever as ações que as APIs estão disponibilizando aos consumidores, apontando o caminho (URI) até aquele ponto, método/verbo (HTTP), informações que precisam ser passadas, formatos suportados, etc.

A ideia é apenas ser apenas informativo, ou seja, isso não será utilizado pelo cliente para a criação automática de um proxy. Pensando nisso, a Microsoft incluiu no ASP.NET Web API a opção para gerar e customizar as documentações de uma API.

Mas a documentação é sempre exibida, na maioria das vezes, de forma amigável ao consumidor, para que ele possa entender cada uma das ações, suas exigências, para que ele possa construir as requisições da forma correta. Sendo assim, podemos na própria aplicação onde nós temos as APIs, criar um controller que retorna uma view (HTML), contendo a descrição das APIs que estão sendo hospedadas naquela mesma aplicação.

public class DeveloperController : Controller
{
    public ActionResult Apis()
    {
        var explorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();

        return View(explorer.ApiDescriptions);
    }
}

Note que estamos recorrendo ao método GetApiExplorer, disponibilizado através da configuração global das APIs. Este método retorna um objeto que implementa a interface IApiExplorer, que como o próprio nome sugere, define a estrutura que permite obter a descrição das APIs. Nativamente já temos uma implementação chamada ApiExplorer, que materializa todoas as APIs em instâncias da classe ApiDescription, e uma coleção deste objeto é retornada através da propriedade ApiDescriptions, e que repassamos para que a view possa renderizar isso.

Na view, tudo o que precisamos fazer é iterar pelo modelo, e cada elemento dentro deste laço representa uma ação específica que está dentro da API. A classe que representa a ação, possui várias propriedades, fornecendo tudo o que é necessário para que os clientes possam consumir qualquer ums destas ações. Abaixo temos o código que percorre e exibe cada uma delas:

@model IEnumerable<System.Web.Http.Description.ApiDescription>
<body>
    @foreach (var descriptor in this.Model)
    {
        <ul>
            <li><b>@descriptor.HttpMethod - @descriptor.RelativePath</b></li>
            <li>Documentation: @descriptor.Documentation</li>

            @if (descriptor.SupportedResponseFormatters.Count > 0)
            {
              <li>Media Types
                <ul>
                  @foreach (var mediaType in descriptor.SupportedResponseFormatters.Select(
                     mt => mt.SupportedMediaTypes.First().MediaType))
                  {
                    <li>@mediaType</li>
                  }
                </ul>
              </li>
            }

            @if (descriptor.ParameterDescriptions.Count > 0)
            {
              <li>Parameters
                  <ul>
                    @foreach (var parameter in descriptor.ParameterDescriptions)
                    {
                      <li>Name: @parameter.Name</li>
                      <li>Type: @parameter.ParameterDescriptor.ParameterType</li>
                      <li>Source: @parameter.Source</li>
                    }
                  </ul>
              </li>
            }
        </ul>
    }
</body>

Ao acessar essa view no navegador, temos a relação de todas as ações que estão expostas pelas APIs. A visibilidade das ações é controlada a partir do atributo ApiExplorerSettingsAttribute, que possui uma propriedade boleana chamada IgnoreApi, que quando definida como True, omite a extração e, consequentemente, a sua visualização.

Dn376302.4BFAEA66868917D9B4744F30BA1BC957(pt-br,MSDN.10).png

Figura 8 - Documentação sendo exibida no browser.

É importante notar que na imagem acima, estamos apresentando a propriedade Documentation. A mensagem que aparece ali é uma customização que podemos fazer para prover essa informação, extraindo-a de algum lugar. Para definir a descrição da ação, vamos criar um atributo customizado para que quando decorado no método, ele será extraído por parte da infraestrutura do ASP.NET, alimentando a propriedade Documentation. O primeiro passo, consiste na criação de um atributo para definir a mensagem:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ApiDocumentationAttribute : Attribute
{
    public ApiDocumentationAttribute(string message)
    {
        this.Message = message;
    }

    public string Message { get; private set; }
}

O próximo passo é decorá-lo em cada uma das ações que quisermos apresentar uma informação/descrição. A classe abaixo representa a nossa API, e o atributo recentemente criado foi decorado em todas as ações, descrevendo suas respectivas funcionalidades:

public class ClientesController : ApiController
{
    [ApiDocumentation("Retorna todos os clientes.")]
    public IEnumerable<Cliente> Get()
    {
        //...
    }

    [ApiDocumentation("Retorna um cliente pelo seu Id.")]
    public Cliente Get(int id)
    {
        //...
    }

    [ApiDocumentation("Inclui um novo cliente.")]
    public void Post(Cliente cliente)
    {
        //...
    }

    [ApiDocumentation("Exclui um cliente existente.")]
    public void Delete(int id)
    {
        //...
    }
}

Só que o atributo por si só não funciona. Precisamos de algum elemento para extrair essa customização que fizemos, e para isso, a temos uma segunda interface, chamada IDocumentationProvider, que fornece dois métodos com o mesmo nome: GetDocumentation. A diferença entre eles é o parâmetro que cada um deles recebe. O primeiro recebe um parâmetro do tipo HttpParameterDescriptor, o que permitirá descrever, também, cada um dos parâmetros de uma determinada ação. Já o segundo método, recebe um parâmetro do tipo HttpActionDescriptor, qual utilizaremos para extrair as informações pertinentes à uma ação específica.

public class ApiDocumentationAttributeProvider : IdocumentationProvider
{
    public string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
    {
        return null;
    }

    public string GetDocumentation(HttpActionDescriptor actionDescriptor)
    {
        var attributes = 
            actionDescriptor.GetCustomAttributes<ApiDocumentationAttribute>();

        if (attributes.Count > 0)
            return attributes.First().Message;

        return null;
    }
}

Aqui extraímos o atributo que criamos, e se ele for encontrado, retornamos o valor definido na propriedade Message. A ausência deste atributo, faz com que um valor nulo seja retornado, fazendo com que nenhuma informação extra seja incluída para a ação.

E, finalmente, para incluir o provedor de documentação ao runtime do ASP.NET Web API, recorremos à configuração das APIs, substituindo qualquer implementação existente para este serviço, para o nosso provedor que extraí a documentação do atributo customizado.

    GlobalConfiguration.Configuration.Services.Replace(
        typeof(IDocumentationProvider),
        new ApiDocumentationAttributeProvider());

Veja também:

ASP.NET Web API – HTTP, REST e o ASP.NET: Para basear todas as funcionalidades expostas pela tecnologia, precisamos ter um conhecimento básico em relação ao que motivou tudo isso, contando um pouco da história e evolução, passando pela estrutura do protocolo HTTP e a relação que tudo isso tem com o ASP.NET.

ASP.NET Web API – Estrutura da API: Entenderemos aqui a template de projeto que o Visual Studio fornece para a construção das APIs, bem como sua estrutura e como ela se relaciona ao protocolo.

ASP.NET Web API – Roteamento: Como o próprio nome diz, o capítulo irá abordar a configuração necessária para que a requisição seja direcionada corretamente para o destino solicitado, preenchendo e validando os parâmetros que são por ele solicitado.

ASP.NET Web API – Hosting: Um capítulo de extrema relevância para a API. É o hosting que dá vida à API, disponibilizando para o consumo por parte dos clientes, e a sua escolha interfere diretamente em escalabilidade, distribuição e gerenciamento. Existem diversas formas de se expor as APIs, e aqui vamos abordar as principais delas.

ASP.NET Web API – Consumo: Como a proposta é ter uma API sendo consumido por qualquer cliente, podem haver os mais diversos meios (bibliotecas) de consumir estas APIs. Este capítulo tem a finalidade de exibir algumas opções que temos para este consumo, incluindo as opções que a Microsoft criou para que seja possível efetuar o consumo por aplicações .NET.

ASP.NET Web API – Formatadores: Os formatadores desempenham um papel importante na API. São eles os responsáveis por avaliar a requisição, extrair o seu conteúdo, e quando a resposta é devolvida ao cliente, ele entra em ação novamente para formatar o conteúdo no formato em que o cliente possa entender. Aqui vamos explorar os formatadores padrões que já estão embuitdos, bem como a criação de um novo.

ASP.NET Web API – Segurança: Como a grande maioria das aplicações, temos também que nos preocupar com a segurança das APIs. E quando falamos de aplicações distribuídas, além da autenticação e autorização, é necessário nos preocuparmos com a segurança das mensagens que são trocadas entre o cliente e o serviço. Este capítulo irá abordar algumas opções que temos disponíveis para tornar as APIs mais seguras.

ASP.NET Web API – Testes e Tracing: Para toda e qualquer aplicação, temos a necessidade de escrever testes para garantir que a mesma se comporte conforme o esperado. Isso não é diferentes com APIs Web. Aqui iremos abordar os recursos, incluindo a própria IDE, para a escrita, gerenciamento e execução dos testes.

ASP.NET Web API – Estensibilidade e Arquitetura: Mesmo que já tenhamos tudo o que precisamos para criar e consumir uma API no ASP.NET Web API, a customização de algum ponto sempre acaba sendo necessária, pois podemos criar mecanismos reutilizáveis, “externalizando-os” do processo de negócio em si. O ASP.NET Web API foi concebido com a estensibilidade em mente, e justamente por isso que existe um capítulo exclusivo para abordar esse assunto.

| Home | Artigos Técnicos | Comunidade