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

Detalhes sobre Tratamento de Erros no ASP.NET MVC

by Israel Aece 19. November 2013 20:46

Desde as primeiras versões do ASP.NET MVC, a Microsoft criou um atributo chamado HandleErrorAttribute, que nada mais do que um filtro (de exceção) e tem a finalidade de interceptar todas as exceções que não são tratadas pelas ações "in-place". E este filtro pode ser aplicado em nível de ação, de controller ou globalmente, no arquivo Global.asax e, este último nível, já vem configurado por padrão nos projetos ASP.NET MVC.

Este atributo garante com que qualquer exceção não seja exibida no navegador, apresentando aquela tela com detalhes do erro e que o usuário final dificilmente entende. Em sua configuração padrão, quando este atributo é acionado, ele redireciona o usuário para uma view chamada Error. Mas tanto o acionamento quanto a view a ser exibida pode ser customizada em qualquer nível, recorrendo às propriedades ExceptionType e View, conforme é mostrado no trecho de código abaixo. É importante dizer que este atributo pode ser decorado múltiplas vezes, possibilitando a seleção da view de acordo com a exceção que foi disparada.

[HandleError(ExceptionType = typeof(DivideByZeroException), View = "DivideByZeroException")]
public class SiteController : Controller { }

Simplesmente o fato de existir este atributo decorado na ação, na classe ou globalmente, não é suficiente para acioná-lo, a não ser que configuremos a seção de customErrors no arquivo Web.Config. Esta seção está presente desde a primeira versão do ASP.NET, e agora pode e deve ser usada em conjunto com o atributo HandleErrorAttribute para garantir ou não o redirecionamento do usuário para a view desejada. A configuração mínima para o correto funcionamento deste atributo deve ser:

<configuration>
  <system.web>
    <customErrors mode="On" />
  </system.web>
</configuration>

E uma vez que o atributo é adicionado, o ASP.NET passará a redirecionar o usuário para a view que exibirá a mensagem amigável. Só que além disso, o ASP.NET MVC faz captura o controller, da ação e da exceção que foi disparada, e configura a instância da classe HandleErrorInfo com todas essas informações referente ao erro que ocorreu, e a adiciona na propriedade ViewData da view que será exibida. Isso irá permitir capturar as informações para exibir na tela caso seja necessário informar ao usuário os detalhes do erro que ocorreu.

@model System.Web.Mvc.HandleErrorInfo

<html>
<head>
    <title>Error</title>
</head>
<body>
    <div>
        <h1>DivideByZeroException</h1>
        <p>
            Controller: <b>@Model.ControllerName</b><br />
            Action: <b>@Model.ActionName</b><br />
            Exception: <b>@Model.Exception.GetType().FullName</b><br />
            Message: <b>@Model.Exception.Message</b><br />
        </p>
    </div>
</body>
</html>

É importante mencionar que o atributo HandlerErrorAttribute somente é acionado, por padrão, quando há erros de número 500 do HTTP. Para outros casos, como 401 (Unauthorized), ele não é acionado e, consequentemente, o erro é exibido para o usuário. Para garantir que seja apresentado, também de forma amigável, outros erros que não sejam do tipo 500, podemos recorrer ao customErrors especificando o erro e a respectiva página de erro que deve ser apresentada quando o mesmo ocorrer.

<customErrors mode="On">
  <error statusCode="401" redirect="/401.htm" />
  <error statusCode="404" redirect="/404.htm" />
</customErrors>

Para finalizar, é importante se atentar em casos onde uma determinada ação (que faz uso deste atributo) é utilizada também por uma aplicação Javascript, pois quando um erro ocorrer, neste cenário o que o cliente está esperando é uma mensagem do erro e não um redirecionamento para uma view. A ideia é que o erro seja tratado pelo próprio cliente (Javascript), que exibirá alguma informação para informar o erro. Uma opção aqui seria herdar da classe HandleErrorAttribute e avaliar se a ação está ou não sendo invocada por uma aplicação Javascript, e para isso, podemos recorrer ao header X-Requested-With, verificando se é igual à XMLHttpRequest, retornando um objeto do tipo JsonResult devidamente configurado.

Tags: , , ,

ASP.NET

Cultura padrão para AppDomain

by Israel Aece 22. September 2013 16:43

O idioma configurado na minha máquina (através do Painel de Controle) é o pt-BR. Ao rodar uma aplicação .NET, por padrão, a mesma é configurada com a cultura que está definida lá, e sendo assim, todas as threads que rodam na aplicação seguirão a mesma cultura. O código abaixo ilustra o código de exemplo:

static void Main(string[] args)
{
    var data = DateTime.Now;
    var valor = 1982.81M;

    ExibirNaMesmaThread(data, valor);
    ExibirEmOutraThread(data, valor);
}

static void ExibirNaMesmaThread(DateTime data, decimal valor)
{
    Console.WriteLine(data);
    Console.WriteLine("{0:C2}", valor);
}

static async void ExibirEmOutraThread(DateTime data, decimal valor)
{
    await Task.Delay(1000);

    Console.WriteLine(data);
    Console.WriteLine("{0:C2}", valor);
}

O resultado ao rodar este código será:

22/09/2013 16:33:34
R$ 1.982,81

22/09/2013 16:33:34
R$ 1.982,81

A data e o valor decimal estão no formato brasileiro, assim como era de se esperar. Em aplicações onde queremos determinar e/ou fixar a cultura padrão, recorremos à propriedade CurrentCulture da classe Thread para isso, e geralmente fazemos a configuração na inicialização da aplicação. Só que essa configuração somente é válida quando estamos em uma aplicação que trabalha apenas com uma única thread. Se desejarmos configurar a cultura explicitamente, fazemos o seguinte:

var cultura = new CultureInfo("en-US");

Thread.CurrentThread.CurrentCulture = cultura;
Thread.CurrentThread.CurrentUICulture = cultura;

Só que ao rodar a aplicação, para nossa surpresa, teremos:

9/22/2013 4:37:58 PM
$1,982.81

22/09/2013 16:37:58
R$ 1.982,81

A primeira exibição mostra os valores no padrão americano, pois está rodando sobre na mesma thread em que configuramos a cultura. Já o método que roda assincronamente e, consequentemente, em outra thread, continua exibindo os valores no formato brasileiro, pois a configuração que fizemos afetou somente a thread corrente, e Task que foi criada pelo .NET para executar o método ExibirEmOutraThread continua utilizando no formato brasileiro.

A partir do .NET Framework 4.5, a classe CultureInfo passa a ter duas novas propriedades estáticas chamadas: DefaultThreadCurrentCulture e DefaultThreadCurrentUICulture, que determinam a cultura padrão para todas as threads que rodam dentro daquele AppDomain. Se optarmos por ela ao invés daquelas propriedades expostas pela classe Thread, teremos ambos os valores formatados igualmente para todos os métodos dentro da aplicação, independentemente de qual thread eles estejam rodando.

var cultura = new CultureInfo("en-US");

CultureInfo.DefaultThreadCurrentCulture = cultura;
CultureInfo.DefaultThreadCurrentUICulture = cultura;

9/22/2013 4:43:19 PM
$1,982.81

9/22/2013 4:43:19 PM
$1,982.81

Tags: ,

.NET Framework | Async

WCF - Suporte à WebSockets

by Israel Aece 22. September 2013 15:42

Em um mundo cada vez mais conectado, cresce a necessidade de termos acesso às informações de forma muito mais rápido, em qualquer lugar, a qualquer momento e em qualquer dispositivo. E quando falamos em ter acesso rapidamente à informações, intrinsicamente estamos pensando em "tempo real" que, teoricamente, assim que a informação chega em algum concentrador, ela deve ser encaminhada à todos os interessados.

Uma vez que temos o provedor de informações, os clientes são meros assinantes que recebem as informações e as exibem ou armazenam para seu uso específico. Quando estamos dentro de um ambiente controlado, como por exemplo, a rede privada de uma empresa e com uma infraestrutura homogênea, podemos recorrer à recursos específicos da tecnologia, como é o caso do TCP ou UDP. Mas nem sempre os provedores e consumidores estão dentro de um mesmo ambiente ou utilizando uma mesma tecnologia. Hoje é comum as empresas dependerem excessivamente de parceiros de negócio que fornecem informações extremamente relavantes para o bom andamento de suas atividades.

Existem algumas técnicas para possibilitar, ou melhor, emular a conexão ativa entre as partes para o envio e recebimento de informações. Entre estas técnicas podems citar a técnica de polling, que consiste em ficar consultando o serviço fornecedor das informações de duas maneiras:

  • Periódico: A cada X minutos enviar uma nova requisição para o serviço em busca de novas informações.
  • Requisição Fixa: Esta técnica consiste no servidor prender a requisição até que uma nova informação esteja disponível. Quando estiver, devolve o resultado para o cliente, que por vez, realiza uma nova requisição e fica, novamente, aguardando um novo resultado.

Ambas as técnicas funcionam e do ponto de vista do usuário acaba sendo transparente. Mas o grande ponto negativo é a latência que teremos, sem contar na possibilidade de prejudicar a escalabilidade na medida em que o número de clientes aumenta. Felizmente os grupos que regem a internet fizeram um trabalho, criando e especificando um - novo - protocolo chamado de WebSockets, que tenta resolver grande parte destes problemas de forma simples e objetiva, sem a necessidade de improvisações que vínhamos fazendo até então.

Basicamente falando, este protocolo tem a finalidade de criar um vínculo entre o cliente o servidor sem a necessidade de realizar múltiplas conexões HTTP, fornecendo, nativamente, uma comunicação bidirecional. Ele faz uso de alguns novos headers que são incluídos no cabeçalho da requisição, e que irão controlar se o cliente e o servidor podem ou não estabelecer uma conexão, e a partir daí, passam a trocar mensagens entre eles. A imagem abaixo ilustra as etapas que ocorrem entre as partes quando utilizamos este protocolo:

  • Passo 1: O cliente envia uma requisição para servidor para iniciar e configurar uma conexão via WebSockets.
  • Passo 2: O servidor aceita a requisição e passa ao cliente uma chave para indicar a efetivação da conexão.
  • Passo 3: A partir da conexão estabelecida, ambos podem enviar mensagem um para o outro.
  • Passo 4: Finalmente, quando o cliente não estiver mais interessado, ele pode encerrar a conexão com o servidor.

Os passos 1 e 2 são conhecidos como handshake, que determina se aquele cliente pode se conectar àquele servidor. Caso positivo, o servidor instrui o cliente à migrar/trocar para o protocolo WebSocket. Mais tarde, ainda neste artigo, vamos monitorar o tráfego entre as partes e analisar as mensagens que são trocadas.

Por se tratar de um protocolo multi-plataforma, todos os grandes fabricantes estão se preocupando em adaptar e desenvolver mecanismos em suas tecnologias e ferramentas para entenderem tal protocolo. A Microsoft se preocupa em adaptar suas tecnologias para que seja possível o desenvolvimento e o consumo de serviços que façam uso deste protocolo. O .NET Framework 4.5 incluiu um namespace chamado System.Net.WebSockets, que fornecem classes de baixo nível para este protocolo. Já as camadas mais acima, tais como o ASP.NET e o WCF fazem uso destas classes para abstrair e facilitar a criação e exposição destes tipos de serviços.

Recapitulando, o WCF fornece um binding chamado WSDualHttpBinding que fornece a conexão duplex sobre o protocolo HTTP, mas este utiliza dois canais para a comunicação e faz uso do protocolo WS-RM (Reliable Messaging) para gerenciar a sessão entre as partes. Além disso, muitas vezes o firewall, quando está devidamente configurado, pode barrar o callback que o serviço envia para o cliente.

Para encapsular toda a comunicação utilizando o WebSockets, a Microsoft incluiu no WCF um novo binding chamado de NetHttpBinding. Este binding fornece uma propriedade interessante chamada TransportUsage que recebe uma das três opções expostas pelo enumerador WebSocketTransportUsage:

  • Always: Força a utilização de WebSockets mesmo quando trata-se de contratos request-reply.
  • Never: Evita o uso de WebSockets, resultando em uma exceção se o contrato for duplex.
  • WhenDuplex: Como o próprio nome indica, será utilizado quando trata-se de um contrato duplex.

É importante dizer que este binding, ao contrário do WSDualHttpBinding que utiliza o WS-RM para gerenciamento da sessão, recorre aos recursos do transporte para gerenciar a mesma. Já a codificação das mensagens (que são SOAP) não são interoperáveis, pois estão em formato binário. Para possibilitar o consumo por outras partes que não são .NET, podemos configurar o binding para que as mensagens sejam codificadas em texto através da propriedade MessageEncoding.

Como exemplo de utilização deste protocolo, vamos criar uma espécie de chat. A criação dos contratos e configuração do serviço para a utilização deste novo protocolo é idêntica aquela que já conhecemos até então. Utilizamos duas interfaces, sendo uma para descrever as ações do chat, e a segunda para determinar como e onde será realizado o retorno (callback) das mensagens.

[ServiceContract(CallbackContract = typeof(IChatCallback))]
public interface IChat
{
    [OperationContract]
    void Entrar(string nome);

    [OperationContract]
    void EnviarMensagem(string de, string para, string mensagem);

    [OperationContract]
    void Sair(string nome);
}

public interface IChatCallback
{
    [OperationContract(IsOneWay = true)]
    void ReceberMensagem(string de, string para, string mensagem);
}

Depois do contrato criado e implementado (vou omitir aqui a classe que representa o serviço, mas ela está no arquivo em anexo para download), temos que expor o serviço para que os consumidores consigam acessar. Novamente, veremos que a estrutura é semelhante aquela que já utilizamos na criação de outros serviços em WCF "tradicionais", sendo a única exceção o binding, que deve ser o NetHttpBinding.

using (var host = new ServiceHost(typeof(Comunicador), 
    new Uri("http://localhost:8282")))
{
    host.AddServiceEndpoint(typeof(IChat), new NetHttpBinding(), "chat");

    host.Open();

    Console.WriteLine("Comunicador no Ar");
    Console.ReadLine();
}

Ao rodar o serviço e analisar os metadados no navegador, veremos que o endereço possui um novo scheme chamado "ws". Nós podemos utilizar tanto o "http" quanto o "ws". Ao referenciar o serviço através da opção "Add Service Reference" em uma aplicação cliente, o WCF já faz a configuração utilizando o "ws", conforme podemos ver no trecho extraído do arquivo de configuração.

<client>
  <endpoint address="ws://localhost:8282/chat"
            binding="netHttpBinding"
            bindingConfiguration="NetHttpBinding_IChat"
            contract="ServicoDeComunicacao.IChat"
            name="NetHttpBinding_IChat" />
</client>

Vou omitir o código da aplicação cliente por não haver nada de especial em relação aquilo que já conhecemos em como consumir um serviço WCF que geram callbacks, mas de qualquer forma, todo o código de exemplo está na arquivo anexado à este artigo. Vamos nos atentar as mensagens que trafegam entre as partes quando o cliente e o servidor entram em ação, e para isso, vamos utilizar o Fiddler para monitorar as requisições, que não deixam de ser HTTP. Quando rodamos o serviço e dois clientes (Israel e Jack), podemos ver a conexão sendo estabelecida com o servidor que gerencia o chat e as mensagens sendo trocadas entre eles:

Por fim, ao monitorarmos as requisições via Fiddler, podemos perceber a conexão sendo criada entre as partes, e tudo isso é definido através de headers no cabeçalho das mensagens, conforme notamos na imagem abaixo. Os primeiros que nos chamam a atenção são o header Connection e Upgrade, que indica ao servidor que a conexão deve ser migrada para WebSockets. Na sequencia temos o header Sec-WebSocket-Key, que trata-se de um hash gerado e codificado em Base64, que em conjunto com o header de resposta chamado Sec-WebSocket-Accept, previnem um ataque conhecido como "cross-protocol attack", e garantem a conexão estabelecida e que as mensagens já podem ser trocadas.

Neste artigo foi possível analisar superficialmente os benefícios e como utilizar este protocolo no WCF. Apesar de útil em alguns cenários, ele pode ser impraticável em outros, como por exemplo, o consumo por aplicações Web (Javascript), que tem certa dificuldade em lidar com mensagens SOAP. Com um pouco de trabalho é possível customizar o serviço para conseguir se comunicar com este tipo de cliente, mas o assunto fica para um próximo artigo.

WCFWebSockets.zip (57.53 kb)

Tags: , , ,

WCF

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

Health Monitoring no ASP.NET MVC

by Israel Aece 1. April 2013 23:19

Há uma funcionalidade que foi criada no ASP.NET 2.0 chamada de Health Monitoring. Este recurso foi desenvolvido para servir como tratador de eventos que ocorrem em uma aplicação web. Utilizando um schema de configuração bastante elaborado, podemos customizar quais tipos de eventos queremos armazenar, onde será salvo, com qual periodicidade, intervalo, etc. Entre os tipos de eventos disponíveis, podemos citar: o sucesso e/ou falha no login, páginas não encontradas, erros não tratados, e assim por diante.

Apesar dela ter sido criada quando ainda não existia o ASP.NET MVC, isso não quer dizer que não pode utilizá-la com este tipo de projeto. O Health Monitoring trata-se de uma funcionalidade de "nível global", onde você poderá utilizar em qualquer tipo de projeto ASP.NET (WebForms ou MVC). Sendo assim, para habilitar este recurso em uma aplicação MVC, basta realizar uma configuração no arquivo Web.config, conforme é mostrado abaixo. Note que estamos optando por armazenar os eventos no Event Log do Windows:

<system.web>
  <healthMonitoring enabled="true">
    <rules>
      <clear />
      <add
        name="All Errors Default"
        eventName="All Errors"
        provider="EventLogProvider"
        profile="Default"
        minInterval="00:01:00"/>
    </rules>
  </healthMonitoring>
</system.web>

Vale lembrar que exceções não tratadas são catalogadas como esperado, porém o ASP.NET MVC possui um atributo chamado HandleErrorAttribute, que tem a finalidade de interceptar as requisições para este atributo quando uma exceção não tratada ocorre dentro da ação que está sendo requisitada, e ele, por sua vez, reencaminha a requisição para uma View padrão chamada Error, para exibir uma mensagem amigável ao usuário.

Para garantir que esta exceção também seja catalogada pelo Health Monitoring, é necessário criarmos um evento customizado, herdando da classe WebRequestErrorEvent. Essa classe será utilizada por uma customização da classe HandleErrorAttribute, onde invocaremos dentro do método OnException, o método Raise da classe WebRequestErrorEvent, passando a ele a exceção capturada pelo ASP.NET. Com isso, todas as exceções não tratadas serão encaminhadas para o atributo CatalogarErroNoHealthMonitoring, catalogadas pelo Health Monitoring e, finalmente, o usuário será redirecionado para a View Error (padrão do MVC).

public class CatalogarErroNoHealthMonitoring : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        base.OnException(filterContext);
        new DescricaoDeErro(this, filterContext.Exception).Raise();
    }
}

public class DescricaoDeErro : WebRequestErrorEvent
{
    private static readonly int eventCode = WebEventCodes.WebExtendedBase + 1;

    public DescricaoDeErro(object eventSource, Exception exception)
        : base("Exceção Não Tratada", eventSource, eventCode, exception) { }
}

Tags: , ,

ASP.NET

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