Características do ASP.NET Web API


Como o ASP.NET Web API é implementando sobre o ASP.NET MVC, muitas das funcionalidades que o MVC possui, foram reutilizadas pelo Web API, para que os mesmos recursos e a mesma forma de configuração sejam equivalentes, não importando se estamos desenvolvendo um site ou uma API para ser exposta e consumida pelos clientes.

Entre as funcionalidades mais comuns, temos o envio de tipos complexos, ou seja, a capacidade das aplicações criarem objetos, definir seus atributos, e submetê-los ao serviço. Isso nos permite criar objetos que descrevem o nosso negócio, tornando bem mais intuitivo que criar um método com vários parâmetros. Além disso, nem sempre o serviço rodará sem qualquer problema; durante a execução vários tipos de exceção podem acontecer, e o tratamento por parte do serviço e a formatação da resposta precisam ser cuidadosamente pensadas, afinal, precisamos no basear nos códigos do HTTP para reportar o problema. Finalmente, muitas vezes as APIs recorrem a recursos externos, tais como: leitura/escrita de arquivos, leitura/escrita de dados no banco de dados, etc., e ao invés do serviço depender diretamente da implementação, podemos fazer com ele dependa de uma abstração, que durante a execução, o objeto concreto é injetado. Para isso, podemos recorrer aos serviços de injeção de dependência, que já estão espalhados pelo ASP.NET Web API, e que nos permitirá customizar várias situações, em pontos distintos.

A ideia deste artigo é introduzir cada um destes tópicos, com detalhes de implementação, ao qual poderemos recorrer durante a construção das nossas APIs.

Model Binding

As aplicações geralmente trabalham com objetos que descrevem suas características, onde estes objetos são manipulados o tempo todo, já que na grande maioria dos casos, ela acaba também sendo persistido no banco de dados, apresentado na tela, etc. Como esses objetos são parte do core da aplicação, é muito comum criarmos formulários que apresente a instância no mesmo na tela (HTML), para que o usuário seja capaz de editá-lo.

Ao submeter o formulário para o servidor, todas as informações (querystrings, body, URI, etc.) chegam através de um dicionário, onde cada valor está associado à uma chave. Ao invés de manualmente construirmos a instância da classe baseada no corpo da requisição, o ASP.NET MVC já fez esse árduo trabalho para nós, e o responsável por isso são os model binders. Baseando-se na action para qual estamos postando a requisição, ele captura o tipo do objeto que precisa ser criado, mapeando o dicionário para cada uma de suas propriedades.

Quando utilizamos o ASP.NET Web API, não é diferente, ou seja, quando o nosso serviço espera um objeto complexo, o ASP.NET faz o trabalho para entregá-lo já materializado para nós. Neste caso, há como recebermos a postagem de um formulário HTML, mas também temos que nos atentar, que na maioria das vezes, vamos lidar com o conteúdo em formato XML ou JSON.

Os formatters, já discutido anteriormente, executam um papel importante aqui. Apesar da tecnologia ter sido readequada para o ASP.NET, os principais formatters foram mantidos: FormUrlEncodedMediaTypeFormatter, JsonMediaTypeFormatter e o XmlMediaTypeFormatter. Baseando-se no header Content-Type da própria requisição, ele escolhe qual destes formatters irá utilizar para extrair as informações do corpo da mensagem, e na sequência, encaminha as informações para que o model binder faça a criação do objeto e, consequentemente, atribua os respectivos valores.

Levando em consideração que temos uma ação que receba como parâmetro a instância da classe Cliente, que por sua vez, possui duas propriedades (Nome e Email), podemos postar o conteúdo em qualquer formato, e desde que haja um formatter para o tipo de conteúdo submetido (Content-Type), o ASP.NET Web API também será capaz de transformá-lo em algo concreto. Abaixo temos a assinatura da ação, e na sequência, os posts efetuados com os diferentes formatos.

[HttpPost]
public void Adicionar(Cliente cliente) { }

[ Via Formulário ]

POST http://localhost:1062/api/clientes/Adicionar HTTP/1.1
User-Agent: Fiddler
Content-Type: application/x-www-form-urlencoded
Host: localhost:1062
Content-Length: 35

Nome=Israel&Email=ia@israelaece.com

[ Via JSON ]

POST http://localhost:1062/api/clientes/Adicionar HTTP/1.1
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:1062
Content-Length: 48

{“Nome”: “Israel”, “Email”: “ia@israelaece.com”}

[ Via XML ]

POST http://localhost:1062/api/clientes/Adicionar HTTP/1.1
User-Agent: Fiddler
Content-Type: application/xml
Host: localhost:1062
Content-Length: 70

<Cliente><Nome>Israel</Nome><Email>ia@israelaece.com</Email></Cliente>

Validações

Uma vez que o model binder foi capaz de construir o objeto baseando-se na requisição, um passo, que pode ser opcional, é a validação desse objeto, que consiste em saber se todas as propriedades do mesmo foram extraídas da requisição, e estão com seus valores definidos conforme esperado.

Novamente, assim como no ASP.NET MVC, o Web API também pode confiar nos 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 System.ComponentModel.DataAnnotations. Só que apenas isso não é suficiente, pois na versão atual (Beta), o ASP.NET Web API não executa automaticamente as validações, e sendo assim, precisamos interceptar a requisição, e antes da ação ser executada, validarmos se o estado do objeto está ou não válido.

Para isso, temos que criar um filtro herdando de ActionFilterAttribute, sobrescrevendo o método OnActionExecuting. Como parâmetro recebemos a instância da classe HttpActionContext, que como o próprio nome diz, traz informações pertinentes ao contexto da requisição atual. Entre as várias propriedades, ela expõe uma chamada de ModelState, que define um dicionário contendo o estado do modelo e determina se o mesmo passou ou não nas eventuais regras de validação que foram definidas.

Com isso, podemos facilmente recuperar os detalhes das validações realizadas, e repassá-las para que o cliente possa ter informações precisas sobre os problemas que aconteceram. Para interrompermos a execução, basta criar uma mensagem de resposta (HttpResponseMessage), definindo que a requisição não está de acordo com o esperado, e atribuí-la a propriedade Response do contexto. Abaixo temos a implementação deste atributo:

public class ModelValidationAtribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext ctx)
    {
        if (!ctx.ModelState.IsValid)
        {
            ctx.Response =
                new HttpResponseMessage<IEnumerable<ErrorDetails>>
                (
                    from e in ctx.ModelState.Values
                    where e.Errors.Count > 0
                    select new ErrorDetails()
                    {
                        Message = e.Errors.First().ErrorMessage
                    },
                    HttpStatusCode.BadRequest
                );
        }
    }
}

Para injetá-lo na execução, podemos decorar diretamente na classe (controller) ou no método (action), ou como é algo comum para qualquer ação do serviço, podemos definí-lo como um filtro global, que automaticamente será executado por toda e qualquer ação que é executada naquela aplicação. O trecho do código abaixo foi adicionado ao arquivo Global.asax, para que o filtro seja adicionado à aplicação durante o início da mesma.

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

Um detalhe interessante é com relação a negociação do conteúdo. Se houver algum problema, é importante que o runtime devolva a resposta ao cliente de acordo com o tipo que ele aceita. Ele olhará para o header Accept para determinar em que formato ele devolverá o conteúdo ao cliente, e com isso, o nosso filtro fica completamente isento em relação à detalhes de formatação da resposta. Abaixo temos a requisição sendo rejeitada pelo serviço:

[ Via JSON ]

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Date: Tue, 06 Mar 2012 11:30:41 GMT
Content-Length: 43

[{“Message”:”The Nome field is required.”}]

[ Via XML ]

HTTP/1.1 400 Bad Request
Content-Type: application/xml; charset=utf-8
Date: Tue, 06 Mar 2012 11:28:46 GMT
Content-Length: 255

<?xml version=”1.0″ encoding=”utf-8″?>
<ArrayOfErrorDetails xmlns:xsi=”…” xmlns:xsd=”…”>
    <ErrorDetails>
        <Message>The Nome field is required.</Message>
    </ErrorDetails>
</ArrayOfErrorDetails>

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.

Tratar as exceções in-place pode não é 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 (400) BadRequest; já se o registro procurado não foi encontrado, ele deverá receber o código 404 (NotFound), e assim por diante.

Da mesma forma que vimos acima, aqui também 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);
    }
}

Assim como no caso do validação que vimos acima, podemos aplicar este atributo em nível de classe (controller) ou de método (action), mas como o tratamento é, geralmente, comum para todos os serviços que rodam dentro da aplicação, então podemos, novamente, aplicar em nível global, assim como é mostrado no código a seguir:

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

Injeção de Dependências

Quando criamos a classe que representará a API, definimos os métodos que serão expostos para os clientes. No interior destes métodos, vamos escrever o código necessário para atender a necessidade dos mesmos, seja calculando, persistindo ou devolvendo alguma informação que é exigida por ele.

Com isso, é extremamente comum que a classe que representa a API necessite recorrer a recursos externos para executar qualquer uma dessas ações, como por exemplo: se ele necessitar salvar alguma informação no banco de dados, precisamos da classe responsável por realizar isso (repositório). O mesmo acontecerá quando ele necessite de alguma informação, pois muito provavelmente, recorreremos a este mesmo repositório.

Todas as funcionalidades e/ou informações que a API precise, e que não seja de responsabilidade dela gerenciar, ela dependerá disso para cumprir o seu objetivo. Com isso, de alguma forma precisaremos injetar essas dependências na API, e com isso, torná-la independente de qualquer implementação concreta, para que se tenha flexibilidade e que facilite os testes.

Como a criação do controller que representa a API é feita pelo próprio ASP.NET, a Microsoft deixou alguns pontos em que podemos customizar a criação do mesmo, e com isso, temos a possibilidade de criarmos a dependência exigida pela API, ou se ainda desejar, recorrer a algum container de injeção de dependência de sua preferência, para que ele contemple e gerencia todas as dependências daquela aplicação.

Incrementando a API que utilizamos acima, ela precisa de um repositório para ler/persistir os clientes no banco de dados. Sendo assim, vamos mudar a API e colocar um construtor exigindo o tal repositório, já que é o mínimo que ela precisa para poder desempenhar a sua atividade.

public class Clientes : ApiController
{
    public Cliente(IRepositorioDeClientes repositorio)
    {
        //…
    }

    [HttpPost]
    public void Adicionar(Cliente cliente)
    {
        //…
        repositorio.Adicionar(cliente);
    }
}

ASP.NET Web API recorre a um resolver, e que podemos fornecer a nossa própria implementação, nos permitindo determinar a forma como construímos as dependnências (incluindo dependências relacionadas à infraestrutura), ou ainda, como disse acima, recorrer à utilização de algum container de DI, e você verá que isso é muito próximo ao padrão que já temos implementado com o ASP.NET MVC. Para customizarmos, basta criarmos uma classe que implemente a interface IDependencyResolver, qual fornece dois métodos autoexplicativos: GetService e GetServices. Abaixo uma implmentação simples deste resolvedor:

public class HardcodeResolver : IDependencyResolver
{
    public object GetService(Type serviceType)
    {
        if (serviceType == typeof(ClientesController))
            return new ClientesController(new RepositorioDeClientes());

        return null;
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return new List<object>();
    }
}

Da mesma forma, como grande parte dos recursos que vimos acima, a criação desta classe não é suficiente para poder funcionar, pois precisamos incorpora-la à execução. O ASP.NET Web API fornece uma classe que gerencia a configuração das APIs que rodam na aplicação, e ela é chamada de HttpConfiguration. Esta classe possui vários membros, e entre eles expõe uma propriedade chamada de ServiceResolver, que como o próprio nome sugere, é utilizada para resolver os serviços requeridos pelo serviço.

Nesta classe existe um método chamado SetResolver, que como parâmetro recebe uma classe que implemente a interface IDependencyResolver. É por este ponto de estensibilidade que plugamos a classe que criamos para resolver as dependências. O código abaixo exibe como procedemos para colocá-la em execução:

GlobalConfiguration
    .Configuration
    .ServiceResolver
    .SetResolver(new HardcodeResolver());

Há também um overload deste mesmo método que nos permite passar, via lambdas, o método que deverá criar a instância das dependências, não precisando assim criar uma classe específica para isso. Abaixo temos a mesmo exemplo só que utilizando esta outra opção:

GlobalConfiguration
    .Configuration
    .ServiceResolver
    .SetResolver
    (
        serviceType =>
        {
            if (serviceType == typeof(ClientesController))
                return new ClientesController(new RepositorioDeClientes());

            return null;
        },
        serviceType => return new List<object>()
    );

Um detalhe importante aqui é que quando a dependência não for resolvida, o ASP.NET Web API espera que retornemos um valor nulo ao invés de disparar uma exceção. Retornando nulo, fará com que o runtime escolha alguma implementação padrão, caso ela exista.

A classe DependencyResolver ainda lida com diversos outros recursos que são utilizados pela própria infraestrutura do ASP.NET Web API. Isso quer dizer que o Web API também recorre a este mesmo objeto, para buscar recursos que ele precisa para funcionar, e com isso, podemos utilizá-lo para inserir as customizações que desenvolvemos quando queremos alterar alguma configuração/comportamento diferente do padrão.

Publicidade

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s