Versionamento de APIs

by Israel Aece 28. April 2014 22:48

Sempre que precisamos desenvolver um serviço para que ele seja consumido (interna ou externamente), a maior dificildade é sempre decidir o que e como expor. A tecnologia em pouco tempo é possível que se tenha conhecimento suficiente para extrair o seu potencial, mas o maior desafio é saber o que expor, e isso muitas vezes está diretamente ligado ao conhecimento que se tem do negócio.

E como se não fosse suficiente, os problemas não param por aí. Depois que o serviço (ou API) esteja no ar, o desafio é outro, seja, a manutenção do mesmo, no que diz respeito a segurança, performance e evolução. A partir do momento em que a API está sendo consumida por, no mínimo, um cliente, uma preocupação passa a ser necessária ao fazer qualquer alteração em sua interface pública, pois dependendo do que é alterado, podemos deixar alguns clientes inoperantes, problema que não tínhamos quando colocamos pela primeira vez a API no ar.

O versionamento da API é importante para caracterizar a evolução da mesma, mas é útil também para que o cliente saiba o que e como está consumindo, e quando uma nova versão entrar no ar, é desejável que se mantenha compatibilidade com os clientes que já fazem uso das versões anteriores, e os novos clientes, já podem usufruir da nova versão sem qualquer restrição.

Quando falamos de API REST, podemos fazer uso de uma das três opções abaixo para identificar a versão, a saber:

  • Headers: inclusão de uma chave no cabeçalho da mensagem.
  • URI: embutir como parte da URI a versão: http://localhost/Documentos/v2/Listar
  • QueryStrings: sufixar a URI com uma informação extra, exemplo: http://localhost/Documentos/Listar?v=2

A primeira opção, que é a utilização da coleção de headers, acaba sendo uma opção bastante interessante, já que não altera a URI e permite manter separado qualquer detalhe de versionamento; já a segunda opção, é bem mais problemática, pois se o cliente salvar localmente o endereço e mais tarde quiser acessá-lo novamente, o servidor ainda terá que responder à esta solicitação, ou seja, sabe-se lá por quanto tempo ainda será necessário manter os dois endereços e, consequentemente, as duas APIs rodando. E por fim, a terceira opção, apesar de menos elegante que a primeira, permite facilmente expressar qual versão da API deseja acessar, sem a manipulação de headers (que pode complicar para alguns clientes) e sem agregar à URI alguma informação que possa prejudicar futuramente.

O ASP.NET Web API permite que você customize a seleção do controller através de um ponto de estensibilidade, sem misturar infraestrutura com regra de negócio. Para isso, podemos recorrer à requisição extraindo as informações (headers, querystrings, etc.) que são necessárias para tomar a decisão de qual controller acessar. Para nosso exemplo, suponhamos que temos um controller que retorna documentos (versão 1.0) e mais tarde, criamos uma nova versão que retorna os mesmos documentos, só que agora incluindo a assinatura de quem o assinou (versão 2.0). A imagem abaixo ilustra os tipos que foram utilizados.

Para que seja possível influenciar na escolha do controller, o primeiro passo para é implementar a interface IHttpControllerSelector, e dentro desta classe escrever a regra necessária para tomar esta decisão. No exemplo abaixo tentamos extrair o header com o nome "Versao"; se encontrado a versão 1.0 ou se nada for encontrado, então retornamos o controller DocumentosController (que é a versão 1.0). Se a versão solicitada pelo cliente for a 2.0, então retornamos a classe DocumentosAssinadosController.

public class SeletorDeControllerDeDocumento : IHttpControllerSelector
{
    private readonly Dictionary<string, HttpControllerDescriptor> controllersConhecidos;
    private const string HeaderDeVersao = "Versao";
    private const string VersaoPadrao = "1.0";

    public SeletorDeControllerDeDocumento(HttpConfiguration config)
    {
        this.controllersConhecidos = new Dictionary<string, HttpControllerDescriptor>()
        {
            { "1.0", new HttpControllerDescriptor(config, "DocumentosController", 
                typeof(DocumentosController)) },
            { "2.0", new HttpControllerDescriptor(config, "DocumentosAssinadosController", 
                typeof(DocumentosAssinadosController)) }
        };
    }

    public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
    {
        return this.controllersConhecidos;
    }

    public HttpControllerDescriptor SelectController(HttpRequestMessage request)
    {
        IEnumerable<string> valores = null;

        if (request.Headers.TryGetValues(HeaderDeVersao, out valores))
            foreach (var item in valores)
                if (controllersConhecidos.ContainsKey(item))
                    return controllersConhecidos[item];

        return controllersConhecidos[VersaoPadrao];
    }
}

Só que esta classe por si só não funciona, ou seja, precisamos acoplá-la à execução, substituindo a implementação padrão que vem com o ASP.NET Web API. Para isso, basta ir até o arquivo Global.asax e fazer o seguinte ajuste:

config.Services.Replace(typeof(IHttpControllerSelector), 
    new SeletorDeControllerDeDocumento(config));

Depois da implementação e da configuração da API, basta executarmos e através de algum cliente (vamos utilizar o Fiddler para os testes), iremos notar a diferença na requisição e, principalmente, na resposta. Como vamos notar, competirá ao cliente expressar qual a versão que ele deseja, e se omitir (pois isso deve ser a configuração padrão dos clientes iniciais), então a versão 1.0 será retornada.

[ Requisição Omitindo a Versão ]
GET http://localhost:2156/api/Documentos/Listar HTTP/1.1
User-Agent: Fiddler
Host: localhost:2156

[ Resposta na Versão 1.0 ]
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 247

[{"Descricao":"Documento1","DataDaAssinatura":"2014-04-23"},{"Descricao":"Documento2","DataDaAssinatura":"2014-04-25"},{"Descricao":"Documento3","DataDaAssinatura":"2014-04-26"}]

[ Requisição na Versão 2.0 ]
GET http://localhost:2156/api/Documentos/Listar HTTP/1.1
User-Agent: Fiddler
Host: localhost:2156
Versao: 2.0

[ Resposta na Versão 2.0 ]
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 307

[{"Assinatura":"AQID","Descricao":"Documento1","DataDaAssinatura":"2014-04-23"},{"Assinatura":"BAUG","Descricao":"Documento2","DataDaAssinatura":"2014-04-25"},{"Assinatura":"BwgJ","Descricao":"Documento3","DataDaAssinatura":"2014-04-26"}]

Tags: , ,

ASP.NET | CSD

Introdução ao ASP.NET Web API - Segunda Edição

by Israel Aece 24. February 2014 20:57

Este e-Book tem a finalidade de introduzir a tecnologia ASP.NET Web API, abordando desde a sua estrutura, arquitetura até as suas principais funcionalidades e utilizações. De uma forma bastante simples, tentei resumir os principais itens, com exemplos práticos de utilização, que podem ser transcritos para um projeto no Visual Studio e executá-los.

Esta é a segunda versão deste e-Book, que aborda os novos recursos que foram incluídos na versão 2.1 do ASP.NET Web API. Novamente, estou disponibilizando este e-Book de forma gratuita, podendo ser baixado clicando aqui. Use-o como quiser. Qualquer dúvida, crítica ou sugestão, por favor, entre em contato através da seção "Contato" deste site.

 

Tags: , , ,

ASP.NET | CSD

Tratamento e Log Global de Exceções - ASP.NET Web API

by Israel Aece 9. February 2014 15:58

Para todo e qualquer tipo de aplicação, uma necessidade que temos é a captura dos erros não tratados pela aplicação e o log destes, para que seja possível a depuração e análise depois que o problema acontecesse, sendo necessário incluir o máximo de informações relevantes para essa apuração.

Apesar de existir algumas técnicas já bastante conhecidas no ASP.NET, a versão 2.1 do ASP.NET Web API, a Microsoft criou um novo namespace chamado System.Web.Http.ExceptionHandling e colocou ali alguns recursos específicos para a interceptação e log das exceções disparadas e não tratadas em qualquer nível da API, ou seja, tanto para as exceções que ocorrem no interior do código dos controllers, bem como os códigos de infraestrutura (formatadores, handlers, etc.).

Para utilizar este serviço de log, devemos recorrer à interface IExceptionLogger, que define um único método chamado LogAsync, que como o próprio nome sugere, já traz suporte para realizar esta tarefa de forma assíncrona, passando como parâmetro para ele uma instância da classe ExceptionLoggerContext, qual contém toda informação pertinente ao problema que ocorreu e onde ocorreu (ExceptionContext). Para facilitar as implementações, também já existe uma classe chamada ExceptionLogger, que implementa esta interface define um método chamado Log, que é onde customizeremos o log da exceção que ocorreu.

Para exemplificar, o código abaixo captura a exceção que ocorreu e armazena em um arquivo texto toda a stack trace. Só que criar a classe não é suficiente, pois precisamos acoplá-la à exceção, e para isso, devemos recorrer novamente ao arquivo Global.asax, que está logo no próximo trecho de código.

public class ExceptionTextLogger : ExceptionLogger
{
    private readonly string filePath;

    public ExceptionTextLogger(string filePath)
    {
        this.filePath = filePath;
    }

    public override void Log(ExceptionLoggerContext context)
    {
        File.AppendAllText(filePath, context.Exception.ToString());
    }
}

config.Services.Add(typeof(IExceptionLogger), 
    new ExceptionTextLogger(@"C:\Temp\Log.txt"));

Quando as exceções ocorrem, lembrando que não são somente aquelas exceções referentes ao código que escrevemos no interior da API, mas incluem também os erros que ocorrem dentro das partes referente à infraestrutura, todos eles são repassados para os loggers configurados na aplicação.

Depois que o log é realizado, entra em cena um novo recurso chamado de exception handler. Este recurso que a Microsoft criou nos permitirá avaliar o erro que ocorreu e determinar se a exceção deverá ou não ser disparada. Caso o handler opte por não tratar o erro que ocorreu, a exceção será lançada. Aqui é feito o uso da classe ExceptionDispatchInfo, que captura o contexto onde ocorreu a exceção e posterga o disparo até que o handler faça a análise da mesma.

Seguindo a mesma estrutura do logger, temos uma interface chamada IExceptionHandler que fornece um método assíncrono chamado HandleAsync, recebendo como parâmetro uma classe chamada ExceptionHandlerContext, que informa os detalhes sobre o problema ocorrido através da propriedade ExceptionContext. E, novamente, temos uma classe base chamada ExceptionHandler com a implementação básica da interface IExceptionHandler, qual já podemos utilizar em nosso código:

public class ExceptionMessageHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        if (context.Exception is BusinessException)
            context.Result = new BusinessBadRequest(context);
    }

    private class BusinessBadRequest : IHttpActionResult
    {
        private readonly ExceptionHandlerContext exceptionContext;

        public BusinessBadRequest(ExceptionHandlerContext exceptionContext)
        {
            this.exceptionContext = exceptionContext;
        }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            return Task.FromResult(
                this.exceptionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, 
                    new HttpError(this.exceptionContext.Exception, true)));
        }
    }
}

O código acima implementa o método Handle e avalia se a exceção que ocorreu foi do tipo BusinessException. Caso tenha sido, ele cria uma mensagem de retorno específica, atribuindo à propriedade Result. Caso essa propriedade não seja abastecida (no nosso caso, se não for uma BusinessException), então o ASP.NET Web API irá disparar o erro. E, finalmente, para colocá-lo em execução, vamos recorrer ao arquivo Global.asax:

config.Services.Replace(typeof(IExceptionHandler), new BusinessExceptionHandler());

Tags: , ,

ASP.NET

Suporte à BSON no ASP.NET Web API

by Israel Aece 27. January 2014 20:49

Já há algum tempo trabalhamos com o formato JSON por ser mais leve e menos verboso quando comparado ao XML, e em tempos onde aplicativos Web estão cada vez mais em evidência, acaba sendo um formato mais simples de lidar quando a manipulação acontecerá através de uma linguagem como o Javascript.

Enquanto as informações retornadas são apenas dados simples (strings, inteiros, decimais, etc.), na maioria das vezes, tudo funciona como esperado, ou seja, com uma boa performance e um conteúdo bem enxuto é trafegado entre as partes (cliente e servidor). O problema começa a acontecer quando precisamos, de alguma forma, retornar informações mais complexas e pesadas, tais como uma informação binária, e aqui, podemos incluir qualquer tipo delas, como por exemplo, uma imagem.

Eis que entra em cena um formato chamado de BSON (Binary JSON), que é uma variação do JSON mas que tem como finalidade serializar as informações em formato binário, otimizando este tipo de conteúdo para trafegar de forma mais reduzida e, consequentemente, mais rápida. Pelo fato de tipos numéricos serem codificados como números e não como strings, a codificação e decodificação é mais eficiente, e além disso, conteúdos binários não precisam ser codificados em Base64, que torna o tamanho da mensagem muito maior do que o normal.

A partir da versão 2.1 do ASP.NET Web API, a Microsoft incluiu um novo formatador chamado BsonMediaTypeFormatter, que pode ser utilizado tanto do lado do servidor quanto do lado do cliente (através do HttpClient), que encapsula a serialização neste formato. Para fazer uso deste novo formatador do lado do servidor, basta acoplá-lo à coleção de formatadores do ASP.NET Web API, através da configuração que está no arquivo Global.asax:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //...

        config.Formatters.Add(new BsonMediaTypeFormatter());
    }
}

Este formatador opera através do mime type application/bson, e através da negociação de conteúdo que o ASP.NET Web API já suporta, ele acaba sendo capaz de gerar o resultado no formato escolhido pelo cliente. Para que seja possível perceber a sua funcionalidade, vamos recorrer à um exemplo que retorna uma imagem (através de um array de bytes) e poderemos visualizar a diferença entre os resultados que são retornados para o cliente através das imagens extraídas do Fiddler. Note que a primeira imagem utiliza JSON e a segunda BSON, que por sua vez, tem uma melhora de cerca de 25%.

Tags: , , ,

ASP.NET

Particionando o Resultado no ASP.NET Web API

by Israel Aece 20. November 2013 12:43

Via de regra, quando fazemos a requisição para algum recurso, ele retorna todo o conteúdo para cliente que solicitou. Enquanto estamos considerando simples porções de dados, não há muito problema em sempre retornar toda a informação sempre que solicitada. O problema pode ocorrer quando estamos retornando uma quantidade considerável de informação em resposta a requisição solicitada. Se durante a transmissão desta grande quantidade de informação a conexão, por algum motivo, for abortada, toda a informação se perde e será necessário recomeçar.

O protocolo HTTP contempla uma funcionalidade chamada de byte serving, que é um processo que faz o particionamento do resultado, enviando uma pequena porção de informação para o cliente, que por sua vez, pode controlar exatamente o que está sendo recebido, e com isso, poderá solicitar ao serviço a parte que ele ainda não tenha, evitando recomeçar do zero. Para formalizar esse tipo de comunicação, o protocolo HTTP faz uso de três headers: Range (requisições), Accept-Ranges e Content-Range (respostas).

Para exemplificar, vamos considerar que queremos fazer uma requisição para uma API que retorna o conteúdo de um arquivo texto, que possui a seguinte informação: 11223344556677889900. Quando o cliente realizar a requisição, ele deverá informar através do header Range a quantidade (em bytes) que ele quer que seja retornado. Ao atender a requisição, o serviço deverá ser capaz de capturar o arquivo em questão e particionar o conteúdo, para servir ao cliente somente o trecho que ele está solicitando. O serviço deverá responder com o status 206 (Partial Content), incluindo o header Content-Range, onde teremos o intervalo (dos dados) que está sendo disponibilizado e também a quantidade total que o conteúdo (o arquivo) possui. Vale lembrar que a propriedade Content-Length sempre refletirá a quantidade de dados que está sendo devolvido no corpo da mensagem e nunca o tamanho total do recurso (do arquivo) que está sendo utilizado.

O header Range deve definir a unidade e o intervalo que será utilizada para calcular a porção de informação que será devolvida. Note nos logs abaixo que está a requisição e sua respectiva resposta. A requisição sempre define o intervalo, e a resposta ratifica o intervalo extraído, contemplando também o tamanho total, para que o cliente possa criar o mecanismo de extração enquanto não recepcionar o conteúdo na íntegra, podendo inclusive criar mecanismos para pausar o download. O que também chama atenção é na última requisição, que apenas colocamos 13- para identificar que queremos o intervalo entre o byte 13 até o final do conteúdo.

-----------------------------------------------------

GET http://localhost:4918/api/Files/Download HTTP/1.1
Host: localhost:4918
Range: bytes=0-7

HTTP/1.1 206 Partial Content
Content-Length: 8
Content-Type: text/txt
Content-Range: bytes 0-7/23

11223

-----------------------------------------------------

GET http://localhost:4918/api/Files/Download HTTP/1.1
Host: localhost:4918
Range: bytes=8-12

HTTP/1.1 206 Partial Content
Content-Length: 5
Content-Type: text/txt
Content-Range: bytes 8-12/23

34455

-----------------------------------------------------

GET http://localhost:4918/api/Files/Download HTTP/1.1
Host: localhost:4918
Range: bytes=13-

HTTP/1.1 206 Partial Content
Content-Length: 10
Content-Type: text/txt
Content-Range: bytes 13-22/23

6677889900

-----------------------------------------------------

A implementação por parte da API não é tão complexa. Basta recorrermos à classe ByteRangeStreamContent, que faz toda a mágica para particionar e devolver somente o que foi solicitado. Obviamente que esta classe recebe como parâmetro um Stream contendo o conteúdo (que pode ser uma imagem, um arquivo texto, etc.) e o intervalo solicitado que pode (e deve) ser extraído da requisição, mas não há necessidade de realizar qualquer cálculo manual, pois de posse do header Range, ela será capaz de realizar todo o procedimento.

public class FilesController : ApiController
{
    [HttpGet]
    public HttpResponseMessage Download()
    {
        return new HttpResponseMessage(HttpStatusCode.PartialContent)
        {
            Content = new ByteRangeStreamContent(
                new FileStream("Dados.txt", FileMode.Open, FileAccess.Read),
                    this.Request.Headers.Range,
                    "text/txt")
        };
    }
}

Tags: , ,

ASP.NET

Processamento em Batch no ASP.NET Web API

by Israel Aece 20. November 2013 09:41

Quando trabalhamos com serviços baseados em HTTP, ele são baseados em um modelo de requisição e resposta, ou seja, para cada solicitação que fazemos ao serviço, ele retorna uma resposta contendo o status ou o resultado da sua solicitação. E ainda falando especificamente sobre o protocolo HTTP, a cada mensagem trocada (na requisição ou na resposta), ela contém um cabeçalho e o corpo da mensagem. O cabeçalho é apenas um dicionário de chave e valor, com informações contextuais e o corpo da mensagem é opcional e pode trazer dados que estão sendo postados ou retornados, serializados em um formato específico.

Independentemente do que esteja dentro da requisição ou da resposta, tudo isso é computado como dados que estão sendo trafegados entre o cliente o serviço, e quando estamos em um ambiente onde o custo da rede é significativo (tanto em termos de performance quanto de custo (se estivermos falando em plano de dados de alguma operadora celular)) podemos ter surpresas durante o funcionamento da aplicação que o consome.

Para tentar reduzir isso, a Microsoft implementou no ASP.NET Web API 2 a possibilidade de realizar requisições em batch. A finalidade desta funcionalidade é basicamente permitir ao cliente empacotar múltiplas requisições em apenas uma, enviar ao serviço que o receberá e entregará as várias requisições para suas respectivas ações (métodos). Depois de processado, o resultado que é devolvido ao cliente também será um outro pacote, contendo o resultado para cada uma das requisições enviadas e processadas. Ambas bibliotecas do ASP.NET Web API (cliente e serviço) já estão preparadas para interagir com este tipo de serviço.

Para exemplificar, vamos utilizar uma API que já está disponível ao criar um projeto ASP.NET Web API: ValuesController. Vou omitir a implementação por questões de espaço, mas o que cada método faz é manipular a coleção estática chamada dados que está declarada no interior desta classe.

public class ValuesController : ApiController
{
    private static List<string> dados = new List<string>();

    public IEnumerable<string> Get() { }

    public string Get(int id) { }

    public void Post([FromBody]string value) { }

    public void Put(int id, [FromBody]string value) { }

    public void Delete(int id) { }
}

O primeiro passo é configurar o serviço para que ele aceite requisições em batch. Tudo começa com a inclusão de um rota que receberá este tipo especial de requisição. E para isso vamos recorrer ao método MapHttpBatchRoute, definindo como handler o DefaultHttpBatchHandler. É este handler, que recebe como parâmetro a instância do HttpServer que ele utilizará para tratar e processar as mensagens internamente, gerando os respectivos resultados e devolvendo para o cliente.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpBatchRoute(
            "BatchApi",
            "api/batch",
            new DefaultHttpBatchHandler(GlobalConfiguration.DefaultServer));

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

Uma preocupação que é sempre levantanda com tudo que é processado em batch, é a sequência em que isso é feito. Por padrão, o DefaultHttpBatchHandler utiliza o modelo sequencial, que como o prório nome sugere, executa as requisições na mesma ordem em que elas foram empacotadas, sendo que ele somente processará a requisições seguinte depois que a anterior finalizar. Caso a ordem não seja relevante para o processamento das requisições, então podemos configurar o handler para realizar o processamento de forma não sequencial e, consequentemente, de forma assíncrona. Para isso, basta recorrer à propriedade ExecutionOrder, escolhendo uma das opções expostas pelo enumerador BatchExecutionOrder:

config.Routes.MapHttpBatchRoute(
    "BatchApi",
    "api/batch",
    new DefaultHttpBatchHandler(GlobalConfiguration.DefaultServer)
    {
        ExecutionOrder = BatchExecutionOrder.NonSequential
    });

Com isso o serviço está pronto para receber requisições em batch. Agora compente aos clientes também se ajustarem para conseguir enviar e receber requisições neste formato. Como já foi mencionado acima, a biblioteca do ASP.NET Web API para consumo de serviços HTTP já possui suporte para isso. A implementação deste recurso foi baseada no tipo de conteúdo conhecido como multipart, que é quando um ou mais conjuntos de dados são combinados em um único body. Assim como uma mensagem HTTP é composta por um cabeçalho, uma linha em branco como separador e um corpo, cada conjunto de dados do multipart (denominado body parts) tem um cabeçalho, uma linha em branco e um corpo. Cada uma dessas partes também possui uma espécie de chave (boundary) que é utilizado para associar essas partes.

O uso por parte do cliente consiste instanciar a classe MultipartContent e adicionar nela as partes, que neste caso são requisições para um mesmo serviço/API. A classe HttpMessageContent recebe em seu construtor a instância da classe HttpRequestMessage, que como sabemos, representa uma requisição HTTP, e devemos realizar a configuração dela como se estivéssemos fazendo a configuração para submete-la diretamente para o HttpClient, mas não é o caso.

private static async Task Run()
{
    using (var client = new HttpClient())
    {
        var requests = 
            new MultipartContent("mixed", "batch_" + Guid.NewGuid().ToString());

        requests.Add(
            new HttpMessageContent(
                new HttpRequestMessage(HttpMethod.Post, "http://localhost:4467/api/values")
        {
            Content = new ObjectContent<string>("Israel", new JsonMediaTypeFormatter())
        }));

        requests.Add(
            new HttpMessageContent(
                new HttpRequestMessage(HttpMethod.Get, "http://localhost:4467/api/values")));

        using (var br = await client.SendAsync(
            new HttpRequestMessage(HttpMethod.Post, "http://localhost:4467/api/batch")
            {
                Content = requests
            }))
        {
            var responses = await br.Content.ReadAsMultipartAsync();
            var postResponse = await responses.Contents[0].ReadAsHttpResponseMessageAsync();
            var getResponse = await responses.Contents[1].ReadAsHttpResponseMessageAsync();

            foreach (var item in await getResponse.Content.ReadAsAsync<IEnumerable<string>>())
                Console.WriteLine(item);
        }
    }
}

É importante notar que uma das mensagens está postando o nome "Israel" para que seja adicionado à coleção, e logo na sequência, estamos empacotando também a chamada para o método Get, que deverá retornar todos os nomes adicionados. Finalmente, depois das requisições que desejamos realizar empacotadas no MultipartContent, então criamos uma terceira requisição (HttpRequestMessage) que levará em seu corpo as requisições POST e GET que configuramos e, como já era de se esperar, esta terceira requisição deve ser direcionada para o endpoint de batch que configuramos no serviço, que saberá como tratar esse tipo de mensagem.

Depois da requisição realizada pelo HttpClient e devolvida pelo serviço, podemos capturar através do método ReadAsMultipartAsync o conteúdo (resposta) da requisição que foi realizada para o endpoint de batch. Da mesma forma que a requisição, a resposta desta requisição também contém em seu interior a resposta para cada um métodos que foram empacotados, e justamente por isso, devemos recorrer à coleção chamada Contents, que através do índice podemos extrair cada uma das respostas que desejarmos.

Com toda essa configuração realizada, se interceptarmos a requisição e resposta podemos visualizar todo o trabalho sendo realizado pelo cliente e pelo serviço, e através das mensagens HTTP que foram trocadas, podemos confirmar todo o procedimento que está sendo realizado para garantir o envio e recebimento correto pelas partes.

[Requisição]

POST http://localhost:4467/api/batch HTTP/1.1
Content-Type: multipart/mixed; boundary="batch_93565607-7bd6-4147-b2eb-27a6b35cd42a"
Host: localhost:4467
Content-Length: 402
Expect: 100-continue
Connection: Keep-Alive

--batch_93565607-7bd6-4147-b2eb-27a6b35cd42a
Content-Type: application/http; msgtype=request

POST /api/values HTTP/1.1
Host: localhost:4467
Content-Type: application/json; charset=utf-8

"Israel"
--batch_93565607-7bd6-4147-b2eb-27a6b35cd42a
Content-Type: application/http; msgtype=request

GET /api/values HTTP/1.1
Host: localhost:4467

--batch_93565607-7bd6-4147-b2eb-27a6b35cd42a--


[Resposta]

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 360
Content-Type: multipart/mixed; boundary="70d3b7ff-6b4c-41ca-aa72-3f2225c19344"
Expires: -1
Server: Microsoft-IIS/8.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 20 Nov 2013 12:20:52 GMT

--70d3b7ff-6b4c-41ca-aa72-3f2225c19344
Content-Type: application/http; msgtype=response

HTTP/1.1 204 No Content


--70d3b7ff-6b4c-41ca-aa72-3f2225c19344
Content-Type: application/http; msgtype=response

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

["Israel","Israel","Israel","Israel"]
--70d3b7ff-6b4c-41ca-aa72-3f2225c19344--

Tags: , ,

ASP.NET

e-Book - Introdução ao ASP.NET Web API

by Israel Aece 1. July 2013 21:16
Este e-Book tem a finalidade de introduzir a tecnologia ASP.NET Web API, abordando desde a sua estrutura, arquitetura até as suas principais funcionalidades e utilizações. De uma forma bastante simples, tentei resumir os principais itens, com exemplos práticos de utilização, que podem ser transcritos para um projeto no Visual Studio e executá-los.

Publicamente quero agradecer imensamente ao Elemar Junior (que dispensa apresentações), por prontamente atender ao meu pedido para escrever o prefácio deste livro. Fiquei extremamente honrado.

Estou disponibilizando este e-Book de forma gratuita, podendo ser baixado clicando aqui. Use-o como quiser. Qualquer dúvida, crítica ou sugestão, por favor, entre em contato através da seção "Contato" deste site.

Tags: , , , ,

ASP.NET | CSD

HttpClient na Portable Class Library

by Israel Aece 8. April 2013 22:19

É comum termos a necessidade de criar uma biblioteca com algum código que seja compartilhado entre diversos projetos. Para isso, recorremos à uma template de projeto chamada Class Library, em que seu output é um arquivo do tipo DLL, que pode ser referenciado e, consequentemente, utilizado por projetos que queiram utilizar a funcionalidade exportada por ela.

Tudo isso funcionará até o momento em que precisarmos expor uma determinada funcionalidade para diferentes plataformas, algo muito comum hoje no desenvolvimento de aplicações dentro do ambiente Microsoft. Possuimos diversos alvos que podemos atingir, como por exemplo: aplicações .NET, aplicações para telefones, aplicações para jogos (XBox), aplicações para tablets, etc. Apesar de existir certa simetria em alguns pontos, em outros já podem não ter tantas semelhanças assim, o que torna difícil o controle manual durante a escrita de uma biblioteca para atender qualquer uma estas plataformas.

Desde 2010 a Microsoft trabalha em um projeto para a criação de bibliotecas que possam ser compartilhadas entre diferentes plataformas, e com o Visual Studio 2012, já temos uma template de projeto exclusiva para isso: Portable Class Library. Ao criar um projeto deste tipo, você será obrigado a informar com quais tipos de projetos deseja que ela seja compatível. Isso garantirá com que você somente utilize membros que sejam comuns entre todas as plataformas selecionadas.

Geralmente algumas informações e funcionalidades estão expostas para serem consumidas remotamente, entre os mais diversos dispositivos. Sendo assim, é muito comum termos a necessidade de consumir os serviços expostos pela própria empresa, parceiros de negócios, etc., afim de reutilizar recursos que já estejam construídos e disponibilizados por terceiros. E como já era de se esperar, APIs REST estão cada vez mais populares, e ter um mecanismo para o consumo de forma simples destes tipos de serviços é essencial em qualquer plataforma.

Sabendo disso, a Microsoft criou uma classe chamada HttpClient, que é utilizada para abstrarir toda a complexidade na interação com APIs REST, e mais recentemente, a Microsoft criou a versão portável desta mesma classe. Com isso, poderemos reutilizar o consumo de APIs REST (HTTP) através de diferentes plataformas, sem a necessidade de reconstruir a cada novo projeto uma ponte de comunicação com estes tipos de serviços, e ainda, sem a necessidade de recorrer de classes de mais baixo nível para atingir o mesmo objetivo.

Em princípio a classe HttpClient fornece as mesmas funcionalidades expostas desde a sua criação, acrescentando alguns poucos métodos que expõem alguma funcionalidade específica para uma determinada plataforma. Para utilizar essa DLL em algum dos seus projetos (incluindo bibliotecas portáveis customizadas (Portable Class Libraries)), basta você adicionar o pacote "Http Client Libraries", conforme mostrado na imagem abaixo:

Tags: , ,

.NET Framework | CSD

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Indicações

Introdução ao ASP.NET Web API - e-Book