Israel Aece

Software Developer

Utilizando o HSTS

Quando falamos em proteger a comunicação entre a aplicação web e os clientes que a consomem, logo pensamos em HTTPS. Este protocolo tem a finalidade de criar um túnel entre as partes, garantindo com que as informações trafeguem entre os dois pontos mantendo a confidencialidade (ninguém além dos dois consegue visualizar) e a integridade (ninguém consegue alterar).

Quando habilitamos este protocolo na nossa aplicação e também no hosting que hospeda a mesma, não é 100% garantido que a aplicação será acessada através de HTTPS, colocando em risco a segurança, pois alguém no meio do caminho pode interceptar a requisição e explorar todo o seu conteúdo, e o pior, começar a direcionar o usuário para sites maliciosos.

Para um teste simples, considere o site do Banco Itaú. Muitas vezes as pessoas não se atentam em acessar o site digitando o https:// no navegador. Se monitorarmos a requisição para o endereço http://www.itau.com.br, nós veremos que o navegador recebe como resposta o código 301 (Moved Permanently), obrigando o navegador a encaminhar o cliente para o endereço seguro, que é o https://www.itau.com.br.



Ao receber este tipo de resposta e houver alguém interceptando a comunicação, ele pode substituir o endereço do redirecionamento e, consequentemente, nos mandar para um site malicioso, que simula a aparência do banco, e rouba todos os nossos dados. Para evitar isso, um novo recurso chamado HTTP Strict Transport Security (ou apenas HSTS) foi criado e permite à uma aplicação web indicar que ela somente pode ser acessada através do protocolo HTTPS, e em conjunto com o navegador, assegurarão que independentemente do que se tente fazer, a requisição sempre será feita (e as vezes até modificada para realizar) através do protocolo seguro. Para o servidor indicar ao navegador a obrigatoriedade da utilização do HTTPS, ele deve incluir no cabeçalho da resposta o item (chave) Strict-Transport-Security, indicando o tempo (em segundos) em que o cabeçalho será mantido do lado do cliente.

A especificação também contempla um parâmetro neste cabeçalho chamado includeSubDomains, que como o próprio nome indica, subdomínios deste site deverão ter a garantia de que também serão acessíveis somente através do HTTPS.

No ASP.NET 5, que é independente de hosting, podemos incluir este cabeçalho através de um middleware. É importante dizer que segundo a especificação, este cabeçalho só deve ser enviado ao cliente quando ele já estiver dentro de uma conexão protegida. E esta validação pode ser feita através da propriedade IsHttps que é exposta pelo objeto que representa a requisição.

public class HstsMiddleware
{
    private readonly RequestDelegate next;
    private readonly TimeSpan maxAge;

    public HstsMiddleware(RequestDelegate next, TimeSpan maxAge)
    {
        this.next = next;
        this.maxAge = maxAge;
    }

    public async Task Invoke(HttpContext context)
    {
        if (maxAge > TimeSpan.Zero && context.Request.IsHttps)
            context.Response.Headers.Append(
                "Strict-Transport-Security", $"max-age={maxAge.TotalSeconds}");

        await next(context);
    }
}

public static class HstsMiddlewareExtensions
{
    public static IApplicationBuilder UseHsts(this IApplicationBuilder builder, TimeSpan maxAge) => 
        builder.UseMiddleware<HstsMiddleware>(maxAge);
}


Alternativamente você também pode recorrer ao IIS para realizar a configuração do HSTS, e permitir com que os recursos que são disponibilizados por ele também sejam protegidos por HTTPS. A solução foi proposta pelo Doug Wilson e consiste em criar uma regra para sobrescrever o protocolo HTTP para HTTPS utilizando o arquivo de configuração da aplicação ou do servidor.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="HTTP to HTTPS redirect" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions>
                        <add input="{HTTPS}" pattern="off" ignoreCase="true" />
                    </conditions>
                    <action type="Redirect" url="https://{HTTP_HOST}/{R:1}"
                        redirectType="Permanent" />
                </rule>
            </rules>
            <outboundRules>
                <rule name="Add Strict-Transport-Security when HTTPS" enabled="true">
                    <match serverVariable="RESPONSE_Strict_Transport_Security"
                        pattern=".*" />
                    <conditions>
                        <add input="{HTTPS}" pattern="on" ignoreCase="true" />
                    </conditions>
                    <action type="Rewrite" value="max-age=31536000" />
                </rule>
            </outboundRules>
        </rewrite>
    </system.webServer>
</configuration>

Preload List

Apesar de garantir que todos os recursos da aplicação serão acessados através do HTTPS, o primeiro problema ainda persiste, ou seja, a requisição inicial para o site do banco pode continuar sendo feita através de HTTP, e a possibilidade de interceptação continua. Para evitar isso, o Google mantém um site (HSTS Preload List) em que podemos adicionar URLs que emitem este cabeçalho, e obviamente, possuem um certificado válido. Isso permitirá ao navegador antes de fazer a requisição para o site avaliar se ele está contido na lista, e se estiver, já transformará a primeira requisição em HTTPS.

Depois da primeira requisição feita, o navegador já é capaz de detectar a necessidade de acessar a aplicação através de HTTPS, e ao acessa-lo via HTTP, o próprio navegador faz um redirecionamento interno (307 - Internal Redirect) e já faz a requisição com HTTPS. É possível ver esse comportamento na imagem abaixo.

Documentação de APIs com Swagger

Há algum tempo eu escrevi aqui sobre o ApiExplorer, que é uma funcionalidade que nos permite expor uma documentação sobre a nossa API. Basicamente ela consiste em nos fornecer metadados dos controllers, ações, parâmetros e resultados para que assim possamos criar uma interface amigável com o usuário (leia-se outro desenvolvedor).

Apesar de termos certa flexibilidade na criação e exibição da documentação, o nosso foco sempre é desenvolvermos a funcionalidade proposta pela API, enquanto a documentação sempre fica em segundo plano, e que na maioria das vezes, nem sempre ganha a atenção que merece. Vale lembrar que é isso que o mundo externo irá utilizar para estudar antes de consumir.

Existe uma especificação chamada Swagger, que tenta padronizar as informações extraídas da APIs Web e, consequentemente, expor de uma forma intuitiva e com bastante riqueza ao mundo. Além do padrão, ainda existe um conjunto de ferramentas (consulte o site oficial), e entre elas, tenho a Swagger UI, que consiste em um conjunto de documentos CSS, HTML e Javascript, e que dinamicamente geram uma interface rica com todas as informações extraídas da API, incluindo o formato das mensagens de requisição e resposta, e ainda, uma opção para já invocar as ações que estão disponíveis, sem precisar recorrer à outras ferramentas, tais como Fiddler. Como um diferencial, os parâmetros já são mapeados e consistidos pela própria interface, garantido que os tipos estejam de acordo com o esperado pela API.

Vale lembrar que o Swagger é completamente independente de plataforma, e não é algo exclusivo do .NET ou da Microsoft. Felizmente existe uma pacote chamado Swashbuckle, que nada mais é que uma implementação desta especificação para Web APIs escritas com o ASP.NET, e que nos bastidores, recorre ao ApiExplorer para gerar a documentação neste - novo - padrão. Por fim, com uma simples linha de código, é possível fazer com que a nossa API tenha uma documentação extremamente rica. Para utiliza-la, basta adicionar o pacote através do Nuget, conforme vemos no comando abaixo:

PM> Install-Package Swashbuckle

Depois de instalado, podemos perceber a criação de uma arquivo chamado SwaggerConfig.cs, que já habilita as configurações mais básicas para o seu funcionamento. Abaixo temos a versão simplificada dela, mas se reparar na classe gerada pela instalação do pacote, há uma série de comentários que são bastante autoexplicativos em relação a cada funcionalidade que ela expõe.

public class SwaggerConfig
{
    public static void Register()
    {
        var thisAssembly = typeof(SwaggerConfig).Assembly;

        GlobalConfiguration.Configuration
            .EnableSwagger(c => c.SingleApiVersion("v1", "Minha API"))
            .EnableSwaggerUi();
    }
}

Com essa configuração realizada, ao rodar a aplicação e colocar /swagger depois do endereço (url) onde está rodando o serviço, teremos a interface com os métodos da nossa API sendo exibidos.



E ao clicar em um dos botões "Try It Out", mais detalhes da requisição e da resposta serão apresentados, e também a ação será invocada na API. Note que se estivéssemos optando por testar uma ação que estivesse sendo exposta através do método HTTP POST, um textarea seria exibido para que você colocasse o corpo da requisição (em JSON ou XML, você escolhe) para assim enviar ao serviço.



Guardando-se as devidas proporções, para quem trabalha ou já trabalhou com SOAP, pode notar certa semelhança com o WSDL (Swagger) e com o SOAP UI (Swagger UI), onde o que vimos aqui, é voltando para o ambiente REST.

Integração do IIS com o DNX.exe

Como sabemos, o ASP.NET 5 é desenvolvido para ser independente de hosting, ou seja, podemos hospedar as aplicações no IIS, via self-hosting (WebListener) e com o, novo hosting, Kestrel, que é multi-plataforma. Em uma nova atualização que a Microsoft liberou do ASP.NET 5 (beta8), foi realizada uma mudança na forma como as aplicações ASP.NET 5 rodam no IIS.

Até então, a Microsoft utilizava um componente chamado "Helios" que servia como ponte entre a estrutura existente do .NET dentro IIS com este novo modelo do ASP.NET 5. Esse componente tinha uma série de responsabilidades, entre elas, fazer a configuração e gestão do runtime (DNX).

A partir da beta8, a Microsoft passou a utilizar o HttpPlataformHandler, que é um módulo nativo (código não gerenciado) que quando instalado no IIS, permite que as requisições HTTP que chegam para ele sejam encaminhadas para um outro processo. E, no caso do ASP.NET 5, esse processo é o dnx.exe (ambiente de execução do ASP.NET 5). Isso garantirá à Microsoft e ao time do projeto, o controle do hosting e a possibilidade de integrá-lo facilmente em ambientes Windows que já utilizam o IIS, tirando o proveito das funcionalidades que ele já disponibiliza. Depois de instalado, já é possível visualizarmos este módulo no IIS:



Quando criamos um novo projeto no Visual Studio, podemos perceber que dentro da pasta wwwroot haverá um arquivo web.config. Vale lembrar que o ASP.NET não utiliza mais este tipo de arquivo para centralizar as configurações; web.config é também o arquivo de configuração do IIS. Dentro deste arquivo temos a configuração do HttpPlataformHandler, que é onde informamos o processo para qual a requisição será encaminhada. Note que o arquivo referencia variáveis de ambiente, mas quando o deployment é feito, ele subistitui pelo endereço até o respectivo local.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="httpPlatformHandler"
           path="*"
           verb="*"
           modules="httpPlatformHandler"
           resourceType="Unspecified"/>
    </handlers>
    <httpPlatform processPath="%DNX_PATH%"
                  arguments="%DNX_ARGS%"
                  stdoutLogEnabled="false"
                  startupTimeLimit="3600"/>
  </system.webServer>
</configuration>

É importante dizer que o comando web continua apontando para o hosting Kestrel. Por padrão, existe um pacote já adicionado chamado Microsoft.AspNet.IISPlatformHandler. Ele é responsável por fornecer um middleware que captura os headers customizados que o IIS passa para o DNX e inclui na coleção de headers da requisição que foi criada dentro deste ambiente.

{
  "dependencies": {
    "Microsoft.AspNet.IISPlatformHandler": "1.0.0-beta8"
  },

  "commands": {
    "web": "Microsoft.AspNet.Server.Kestrel"
  }
}

Esse middleware é adicionado através do método UseIISPlatformHandler, e faz a cópia de alguns cabeçalhos (X-Forwarded-Proto, X-Original-Proto, etc.) utilizados para acompanha-la durante o repasse entre os ambientes. Ele também já faz a mescla da identidade (ClaimsPrincipal) repassada do IIS com a identidade da aplicação, caso o usuário já esteja autenticado deste lado.

Quando a aplicação é instalada no IIS, como falando acima, o web.config sobre algumas mudanças e ajusta os caminhos para o endereço de onde está o executável do DNX:

<httpPlatform processPath="..\approot\web.cmd" ... />

Finalmente, ao rodar a aplicação, é possível percebermos que o dnx.exe está abaixo do w3wp.exe, que é o processo que o IIS utiliza para rodar cada pool de aplicação. As funcionalidades fornecidas pelo IIS continuam disponíveis, como reciclagem do processo, inatividade, etc. O interessante também é que o pool não precisa mais rodar sobre código gerenciado, e ele também não será mais o responsável por fazer o carregando do runtime (CLR). Isso passa a ser responsabilidade do processo que está abaixo dele. Nem mesmo as funcionalidades do ASP.NET fornecidas pelo Windows precisam ser mais instaladas; a aplicação já é toda "auto-contida" (código, pacotes e runtime).


Request Features no ASP.NET

O ASP.NET 5 está sendo construído para ser completamente independente do hosting em que ele está sendo executado, permitindo assim, que este tipo de aplicação seja também levado para outras plataformas (Linux e Mac). Atualmente o ASP.NET 5 pode ser hospedado no IIS (incluindo a versão Express), via self-host (WebListener) ou através do Kestrel (que é um servidor multi-plataforma baseado no libuv.

Mesmo que seja independente do hosting, o ASP.NET fornece um conjunto de tipos que permite interagir, de forma genérica e abstrata, com as funcionalidades que são expostas pelo HTTP, e que na maioria das vezes, são criadas e abastecidas pelo receptor da requisição, ou seja, o próprio hosting. Esses tipos são chamados de request features, e estão incluídas no pacote chamado Microsoft.AspNet.Http.Features. Entre os tipos (funcionalidades) disponíveis temos as interfaces IHttpRequestFeatureIHttpResponseFeature, que representam o básico para o funcionamento de qualquer servidor web, que é receber a requisição, processar e devolver o resultado. Um outro exemplo é a interface ITlsConnectionFeature, que pode ser utilizada para interagir com os certificados enviados pelo cliente. Para uma relação completa, consulte a documentação.

Cada um dos servidores disponíveis já implementam as interfaces definidas (desde que eles suportem), e nos permite acessar essas funcionalidades durante a execução da aplicação, incluindo a possibilidade de acesso através de qualquer middleware. A classe HttpContext expõe uma propriedade chamada Features, que nada mais é que uma coleção das funcionalidades que são incluídas (e, novamente, suportadas) pelo hosting onde a aplicação está rodando. Essa coleção fornece um método genérico chamado Get<TFeature>, onde podemos definir a funcionalidade que queremos acessar. Abaixo um trecho do código de exemplo:

public Task Invoke(HttpContext httpContext)
{
    var clientCertificate =
        httpContext.Features.Get<ITlsConnectionFeature>()?.ClientCertificate;

    if (clientCertificate != null)
    {
        //...
    }

    return _next(httpContext);
}

Além da possibilidade de extrair alguma funcionalidade, é possível também incluir funcionalidade durante a execução, e como já era de se esperar, basta utilizar o método, também genérico, Set<TFeature>. E o mais interessante é que não estamos restritos a utilizar uma das interfaces expostas pelo ASP.NET; podemos criar novas funcionalidades e incorporá-las na execução.

Considere o exemplo abaixo, onde criamos uma funcionalidade para simular a proteção da requisição por uma transação. Basta utilizarmos uma interface para descrever a funcionalidade e criar uma classe que a implemente.

public interface ITransactionFeature
{
    string TransactionId { get; }
    void Commit();
    void Abort();
}

public class TransactionFeatureContext : ITransactionFeature
{
    public string TransactionId { get; } = Guid.NewGuid().ToString();

    public void Commit()
    {
        //...
    }

    public void Abort()
    {
       //...
    }
}

A classe TransactionFeatureContext faz toda a gestão da transação, desde a sua criação (identificando-a) e expõe métodos para conclusão com sucesso (Commit) ou falha (Abort). Repare que tanto a interface quanto a classe não referenciam nenhum tipo do ASP.NET; estamos livres para determinarmos toda a estrutura da nossa funcionalidade. Só que estas classes precisam ser incorporadas na execução, e para isso, vamos criar um middelware que incluirá esta funcionalidade na coleção de Features do contexto da requisição.

public class TransactionMiddleware
{
    private readonly RequestDelegate _next;

    public TransactionMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        var transactionContext = new TransactionFeatureContext();
        httpContext.Features.Set<ITransactionFeature>(transactionContext);

        try
        {
            await _next(httpContext);
            transactionContext.Commit();
        }
        catch
        {
            transactionContext.Abort();
            throw;
        }
    }
}

O middleware é criado é os middlewares internos que serão invocados a partir dele estarão sendo gerenciados pela transação. Qualquer exceção não tratada que ocorra, a transação será automaticamente abortada. Por fim, depois do middleware criado, incluímos ele na aplicação através do método UseMiddleware.

Vale lembrar que a ordem em que se adiciona os middlewares são importantes. Como a classe TransactionMiddleware adiciona a feature TransactionFeature que criamos, os middlewares que vem a seguir podem acessar a funcionalidade através do método Get<TFeature>, conforme falado acima. Opcionalmente podemos criar na interface métodos para que os middlewares da sequência possam votar no sucesso ou falha e este, por sua vez, avalia os votos antes de chamar o método Commit.

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();
    app.UseTransactionMiddleware();
    app.UseClientCertificateLogMiddleware();
    app.UseMvc();
}

Convenções no ASP.NET

Durante a construção e evolução do ASP.NET MVC e Web API a Microsoft foi assumindo algumas regras, que durante a execução, os frameworks alteram e geram informações de acordo com o código escrito por nós. Isso é conhecido como convention-over-configuration, e tem como foco diminuir algumas configurações explícitas por parte do desenvolvedor, focando em detalhes mais importantes para atender o negócio e o framework se responsabiliza por realizar as configurações necessárias para o funcionamento daquele código respeitando as tais convenções.

Apesar de as vezes não notarmos, isso está bastante presente em nosso dia a dia. Quando criamos um controller, dentro dele uma ação e esta, por sua vez, retornarmos uma view, o ASP.NET MVC assume a estrutura física de Views/NomeDoController/NomeDaAcao.cshtml para armazenar o arquivo e localiza-lo para exibir para o cliente. Outro exemplo é quando utilizamos o Entity Framework e queremos que a entidade Cliente seja persistida na base de dados; o ORM irá criar, automaticamente, a tabela com o mesmo nome daquela entidade. Já no ASP.NET Web API, se criarmos uma ação com o método chamado Get, ele estará automaticamente acessível através do verbo GET do HTTP, sem precisar decorar o mesmo com o atributo HttpGetAttribute.

Note que nos dois exemplos a Microsoft assumiu essas regras e as asseguram durante a execução, ficando, de certa forma, transparente para nós. Se não houvesse isso, teríamos que configurar as mesmas coisas em 100% dos casos; com essas convenções predefinidas, precisamos apenas nos preocupar quando queremos algo diferente do padrão.

O ASP.NET MVC e Web API expõem tipos para que possamos customizar o framework de acordo com nossas necessidades. Até então, isso poderia ser realizado customizando alguns serviços de baixo nível (controller factory, por exemplo), mas o problema desta técnica é que estaremos tocando em outras atividades e que qualquer descuido poderemos ter problemas ao executar e, consequentemente, não atingirmos o objetivo esperado.

O ASP.NET 5 simplifica bastante essa customização expondo uma interface chamada IApplicationModelConvention. Ao implementar ela e acopla-la a execução, o ASP.NET identificará todos os controllers, ações, rotas e filtros candidatos a serem expostos (de acordo com a convenção predefinida) e entregará para analisarmos (através da classe ApplicationModel), e com isso, podemos customizar da forma que precisamos. Tudo isso através de uma API de mais fácil utilização, sem a necessidade de conhecer detalhes dos bastidores para implementar convenções que estão em um nível mais superficial.

Para exemplificar o uso, considere o controller abaixo, que possui duas ações e que retornam uma string. Como podemos perceber, o código foi escrito utilizando o português. Por convenção, o ASP.NET Web API assumirá que o nome dos membros (classe e métodos) serão os mesmos que serão expostos e utilizados pelo sistema de roteamento para montar os links de acesso à API e suas ações.

public class UtilitariosController : Controller
{
    [HttpGet]
    public string ExecutarTarefa()
    {
        return "..." ;
    }

    [HttpGet]
    public string EnviarEmail()
    {
        return "...";
    }
}

A ideia é deixar o desenvolvedor livre para codificar no idioma desejado, e através do recurso de convenção, vamos estipular que o framework irá traduzir de acordo com a escolha realizada durante a sua configuração. Na ausência do idioma, a nomenclatura utilizada no código será exposta.

O que fazemos abaixo é no método Apply (único, exposto pela interface IApplicationModelConvention) percorremos os controllers encontrados pelo ASP.NET, procuramos pela chave no dicionário (que em um ambiente real, poderíamos estar recorrendo à algum outro repositório ao invés de hard-code), e por fim substituímos o nome do controller e da ação pelo encontrado no dicionário, baseado na cultura escolhida.

public class LocalizedApiNamingModelConvention : IApplicationModelConvention
{
    private string culture;

    private IDictionary<string, Tuple<string, string>> mapping =
        new Dictionary<string, Tuple<string, string>>();

    public LocalizedApiNamingModelConvention(string culture)
    {
        this.culture = culture;

        mapping.Add(
            "en-US.Utilitarios.ExecutarTarefa",
            Tuple.Create("Utilities", "RunTask"));

        mapping.Add(
            "en-US.Utilitarios.EnviarEmail",
            Tuple.Create("Utilities", "SendEmail"));
    }

    public void Apply(ApplicationModel application)
    {
        foreach (var c in application.Controllers)
        {
            var controllerName = c.ControllerName;

            foreach (var a in c.Actions)
            {
                var key = BuildKey(controllerName, a.ActionName);

                if (this.mapping.ContainsKey(key))
                {
                    var info = this.mapping[key];

                    c.ControllerName = info.Item1;
                    a.ActionName = info.Item2;
                }
            }
        }
    }

    private string BuildKey(string controllerName, string actionName)
    {
        return $"{culture}.{controllerName}.{actionName}";
    }
}

Depois da classe criada, basta incluirmos na coleção chamada Conventions, e fazemos isso através do método AddMvcCore, conforme é mostrado no código abaixo:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore(o =>
        o.Conventions.Add(new LocalizedApiNamingModelConvention("en-US")));
}

Repare que criamos a classe e no construtor passamos a cultura en-US e ao rodar a aplicação, os links acessíveis serão /Utilities/RunTask e /Utilities/SendEmail. Os links em português retornarão o erro 404 do HTTP (Not Found).

Testes com a classe TelemetryClient

No artigo anterior eu mostrei o uso da classe TelemetryClient para catalogar informações customizadas. O fato de utilizar a classe em sua configuração padrão, fará com que as informações sejam enviadas para o serviço (na nuvem) mesmo que você ainda esteja em ambiente de testes. Talvez isso não seja o que estamos querendo, pois espalhamos pelo código os pontos que são cruciais para enviar as informações, mas talvez só faça sentido quando estive em ambiente de produção.

A classe TelemetryClient confia nas configurações que são disponibilizadas pela classe TelemetryConfiguration. Ela, por sua vez, possui uma propriedade estática chamada Active que retorna as configurações globais (para a aplicação) do Application Insights. A partir daí podemos recorrer a propriedade DisableTelemetry que quando definida como true, não enviará as informações ao serviço. Para utiliza-la podemos também condicionar à diretiva de DEBUG para somente desabilitar enquanto estivermos em desenvolvimento:

#if DEBUG
            TelemetryConfiguration.Active.DisableTelemetry = true;
#endif

Ainda com o exemplo utilizado no artigo anterior, não estávamos nos preocupando com os testes unitários. É uma preocupação que também devemos ter para não reportar os eventos quando estamos rodando os testes. Apesar da opção acima resolver, as vezes podemos querer validar se o nosso código está ou não reportando quando necessário e se as informações que foram levadas estão de acordo com a nossa expectativa.

A classe de configuração também nos permite configurar o canal de comunicação, e com isso customizar (sobrescrever) onde e como queremos armazenar os logs. Só que antes de visualizarmos o código necessário para alcançar isso, vamos entender a dependência entre a classe TelemetryClient e o restante da configuração.



A classe TelemetryClient possui dois overloads, onde um deles espera a instância da classe TelemetryConfiguration, que se for omitida, o Application Insights utiliza o valor fornecido pela propriedade Active. A classe TelemetryConfiguration possui uma propriedade chamada TelemetryChannel, onde podemos customizar o canal de comunicação através da implementação da interface ITelemetryChannel. Para o exemplo, vou optar por armazenar os logs em uma coleção interna, conforme é possível ver no código abaixo:

public class TestChannel : ITelemetryChannel
{
    private readonly IList<ITelemetry> itens;

    public TestChannel()
    {
        this.itens = new List<ITelemetry>();
    }

    public void Send(ITelemetry item)
    {
        this.itens.Add(item);
    }

    public IList<ITelemetry> Itens
    {
        get
        {
            return this.itens;
        }
    }

    //outros membros, omitidos por questão de espaço
}

Depois da classe implementada, precisamos criar a instância e associa-la à propriedade da configuração para que ela passe a ser utilizada quando chamarmos os métodos para catalogar as informações (Track*). O código que foi criado para calcular o frete não foi alterado em nada. Mesmo que a configuração tenha sido realizada externamente, a classe TelemetryClient irá utilizar esta classe via TelemetryConfiguration (via propriedade Active). O teste ficaria da seguinte forma:

[TestClass]
public class CalculadoraDeFrete
{
    private TestChannel channel;

    [TestInitialize]
    public void Inicializar()
    {
        this.channel = new TestChannel();
        TelemetryConfiguration.Active.TelemetryChannel = channel;
    }

    [TestMethod]
    public void DeveCalcularFreteParaValinhos()
    {
        var cep = "13273-047";
        var valorEsperado = 12.38M;

        var calculadora = new CalculadoraDeFrete();
        var resultado = calculadora.Calcular(cep);

        Assert.AreEqual(valorEsperado, resultado);

        var evento = this.channel.Itens.Single() as EventTelemetry;

        Assert.AreEqual("CalculoDeFrete", evento.Name);
        Assert.AreEqual(cep, evento.Properties["Cep"]);
        Assert.AreEqual(resultado.ToString(), evento.Properties["Resultado"]);
    }
}

ApplicationInsights - Utilizando a classe TelemetryClient

A Microsoft está disponibilizando um serviço de telemetria de aplicações chamado de Application Insights. É um serviço que roda no Azure (nuvem) e que permite integrar nas aplicações que desenvolvemos para que seja possível extrair informações, armazenar e, consequentemente, analisar os dados gerados para tentar identificar algum problema de performance ou de qualquer outra natureza.

Dependendo do tipo de aplicações onde este serviço é instalado, ele utiliza o recurso exposto pela aplicação para interceptar a requisição e catalogar as informações. Por exemplo, se este serviço é instalado em uma aplicação ASP.NET, então o módulo (IHttpModule) ApplicationInsightsHttpModule é incluído para interceptar as requisições que chegam à aplicação; já para aplicações ASP.NET 5, acopla-se através do método UseApplicationInsightsRequestTelemetry. Em ambos os casos, automaticamente o Application Insights começa a receber informações sobre as requisições que chegam para a aplicação, duração da requisição, cabeçalho, etc.

A identificação da aplicação para o serviço é uma chave chamada instrumentation key (GUID) que é gerada pelo Azure, e deve ser configurada na aplicação para que o Application Insights possa enviar ao serviço e ele, por sua vez, saiba qual aplicação está gerando os dados. E, novamente, dependendo da tecnologia, o arquivo utilizado para armazenar a chave é o ApplicationInsights.config para os projetos ASP.NET tradicionais ou aplicações Windows, e o config.json para o ASP.NET 5.

[ApplicationInsights.config]
<?xml version="1.0" encoding="utf-8"?>
<ApplicationInsights>
  ...
  <InstrumentationKey>bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed</InstrumentationKey>
</ApplicationInsights>

[config.json]
{
  "ApplicationInsights": {
    "InstrumentationKey": "bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed"
  },
  ...
  }
}

Mesmo que ocorra essa mágica, o Application Insights fornece uma API que podemos recorrer para customizar os dados que são enviados para o serviço. Isso pode ser feito através da classe TelemetryClient, que expõe alguns métodos específicos ou de mais baixo nível para customizar diversas informações que podem ser levadas para o serviço. Para fazer uso desta classe, primeiramente precisamos utilizar o Nuget para adicionar o seguinte pacote:

Install-Package Microsoft.ApplicationInsights

É importante dizer que é possível fazer uso desta classe em projetos que não sejam do tipo ASP.NET. Como exemplo, vou utilizar uma aplicação Console para catalogar os mais diversos tipos de informações no Application Insights. No caso de projetos ASP.NET, já há uma opção durante a criação do projeto para habilita-lo, assim como é possível vermos na imagem abaixo, e a instalação explícita do pacote não é necessária.

>

Independente da forma como você adiciona o suporte para o Application Insights na aplicação, a classe TelemetryClient pode ser utilizada. Como já era de se esperar, esta classe fornece uma propriedade chamada InstrumentationKey, que é onde devemos configurar a chave gerado pelo Azure, mas em casos de projetos do tipo Windows, podemos criar o arquivo ApplicationInsights.config e lá ter o elemento InstrumentationKey, o que facilita a alteração sem precisar recompilar a aplicação.

var tc = new TelemetryClient()
{
    InstrumentationKey = "bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed"
};

A partir da instância da classe TelemetryClient criada, temos alguns métodos para enviar informações para o serviço. O Application Insights possui diversos tipos de informações ("categorias") que possamos enviar os dados: Trace, Request, Page View, Expcetion, Dependency e Custom Event. Para cada uma destas informações, há um método correspondente: TrackTrace, TrackRequest, TrackPageView, TrackException, TrackDependency e TrackEvent.

Para exemplificar, considere o código abaixo. Estamos utilizando o método TrackEvent para indicar ao serviço que trata-se de um evento que vamos gerar diversas entradas e que corresponde à uma atividade de rotina da nossa aplicação. Além do nome, também podemos, opcionalmente, informar um dicionário de dados contendo propriedades que queremos anexar aquele evento. No exemplo, estou optando por incluir o parâmetro, o resultado e o tempo que levou para realizar o cálculo.

public class CalculadoraDeFrete
{
    private static TelemetryClient tc = new TelemetryClient()
    {
        InstrumentationKey = "bab34a9e-f1d9-4cf9-8a09-5f33b8f4ebed"
    };

    public static decimal Calcular(string cep)
    {
        var sw = Stopwatch.StartNew();

        //cálculo do frete
        var resultado = 12.38M;

        tc.TrackEvent(
            "CalculoDeFrete",
            new Dictionary<string, string>()
            {
                { "Cep", cep },
                { "Resultado", resultado.ToString() },
                { "Tempo", sw.Elapsed.ToString() }
            });
        tc.Flush();

        return resultado;
    }
}

Ainda em relação as propriedades que podemos anexar ao log no momento em que o evento ocorre, podemos também definir valores globais que serão enviados em todas as requisições, independentemente se está catalogando um evento, uma exceção, ou qualquer outra informação. E para isso, basta recorrer ao dicionário que está exposto através do contexto da classe TelemetryClient:

static CalculadoraDeFrete()
{
    tc.Context.Properties.Add("Usuario", "Israel Aece");
}

Internamente a classe mantém um buffer com as entradas e periodicamente envia as requisições para o serviço. Utilizamos o método Flush para adiantar esse processo. Se consultarmos o portal, já podemos visualizar o evento adicionado e as respectivas propriedades que enviamos (incluindo a propriedade global Usuario). A partir deste nome do evento, eles sempre serão agrupados para uma melhor visualização e análise.



Como foi comentado acima, também é possível realizar o log de uma exceção. Para isso, podemos envolver o código inseguro em um bloco try/catch e utilizar o método TrackException. Estou optando por utilizar a versão mais simples do método, mas há parâmetros opcionais que permitem incluir um dicionário com valores customizados, assim como fizemos no código acima.

public static decimal Calcular(string cep)
{
    var resultado = 0M;

    try
    {
        throw new ArgumentException("O CEP informado está fora da área de cobertura da transportadora.");
        //cálculo do frete
    }
    catch (Exception e)
    {
        tc.TrackException(e);
        tc.Flush();
        throw;
    }

    return resultado;
}

Agora se atualizarmos o portal do Azure, irmos até a seção de Failures, o contador foi incrementado e é possível visualizar o erro que ocorreu, incluindo além da mensagem, toda a stack trace, útil para nós desenvolvedores, identificar onde o problema exatamente aconteceu.



Como é um exemplo simplista, estou fazendo o tratando localizado da exceção. Dependendo da estratégia de tratamento de erros da aplicação, é possível centralizar o envio das informações do erro em um ponto global, e dependendo da tecnologia utilizada, você pode recorrer ao suporte que ela dá para isso. Em aplicações ASP.NET tradicionais, você pode concentrar isso no evento Application_Error no arquivo Global.asax, no ASP.NET MVC em um filtro, no ASP.NET Web API no ExceptionLogger ou, no WCF, através da interface IErrorHandler.

Como podemos notar, o Application Insights eleva o "simples log de aplicações" para um outro nível. Podemos gerar diversas informações e temos a certeza que por trás existe uma grande infraestrutura para armazenamento e processamento delas. Terceirizando isso nos permite ainda mais focar no que de fato importa: o desenvolvimento da regra de negócio para qual a aplicação está sendo construída.

Web Sockets com ASP.NET Web API

Há algum tempo eu comentei sobre a possibilidade do WCF expor serviços para serem consumidos utilizando a tecnologia de Web Sockets. Neste mesmo artigo eu falei sobre os detalhes do funcionamento deste protocolo, e que se quiser saber o seu funcionamento e implementação em um nível mais baixo, aconselho a leitura.

Como sabemos, o WCF é uma tecnologia que roda do lado do servidor. Da mesma forma que temos o ASP.NET Web API, que é uma tecnologia que utilizamos para criarmos APIs e expor para os mais diversos tipos de clientes. Assim como a Microsoft adicionou o suporte à web sockets no WCF, ela também fez o mesmo com o ASP.NET Web API e MVC, ou seja, incorporou no próprio framework o suporte para criação de recursos que são expostos utilizando web sockets.

É importante dizer que a Microsoft também criou uma outra tecnologia chamada de SignalR, que fornece diversos recursos para a criação de aplicações que precisam gerar e consumir informações que são consideradas de tempo real. Este framework já possui classes que abstraem a complexidade de exposição destes tipos de serviços, fornecendo classes de mais alto nível para trabalho.

Entre os novos tipos que foram adicionados, temos a propriedade IsWebSocketRequest (exposta pela classe HttpContext), que retorna um valor boleano indicando se a requisição está solicitando a migração do protocolo para web sockets. Caso seja verdadeiro, então recorremos ao método AcceptWebSocketRequest da mesma classe para especificarmos a função que irá periodicamente ser executada.

Para o exemplo, vamos criar um publicador de notícias, que quando o cliente optar por assiná-lo, ele irá receber as notícias de forma randômica em um banner a cada 2 segundos. Como podemos ver no código abaixo, o método Assinar da API é exposto para que os clientes cheguem até ele através do método GET do HTTP. Como se trata de uma solicitação de migração, retornamos o código 101 do HTTP indicando a troca do protocolo para web sockets.

public class PublicadorController : ApiController
{
    private static IList<string> noticiais;
    private static Random random;

    static PublicadorController()
    {
        noticiais = new List<string>()
        {
            "You can buy a fingerprint reader keyboard for your Surface Pro 3",
            "Microsoft's new activity tracker is the $249 Microsoft Band",
            "Microsoft's Lumia 950 is the new flagship Windows phone",
            "Windows 10 will start rolling out to phones in December",
            "Microsoft's Panos Panay is pumped about everything (2012–present)"
        };

        random = new Random();
    }

    [HttpGet]
    public HttpResponseMessage Assinar()
    {
        var httpContext = Request.Properties["MS_HttpContext"] as HttpContextBase;

        if (httpContext.IsWebSocketRequest)
            httpContext.AcceptWebSocketRequest(EnviarNoticias);

        return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
    }

    private async Task EnviarNoticias(AspNetWebSocketContext context)
    {
        var socket = context.WebSocket;

        while (true)
        {
            await Task.Delay(2000);

            if (socket.State == WebSocketState.Open)
            {
                var noticia = noticiais[random.Next(0, noticiais.Count - 1)];
                var buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(noticia));

                await socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
            }
            else
            {
                break;
            }
        }
    }
}

Já dentro do método EnviarNoticias criamos um laço infinito que fica selecionando randomicamente uma das notícias do repositório e enviando para o(s) cliente(s) conectado(s). É claro que também podemos receber informações dos clientes conectados. Para o exemplo estou me limitando a apenas gerar o conteúdo de retorno, sem receber nada. Se quiser, pode utilizar o método ReceiveAsync da classe WebSocket para ter acesso à informação enviada por ele. E, por fim, para não ficar eternamente rodando o código, avaliamos a cada iteração se o estado da conexão ainda continua aberto, caso contrário, encerramos o mesmo.

E do lado do cliente, se for uma aplicação web, vamos recorrer ao código Javascript para consumir este serviço. Temos também a disposição deste lado a classe WebSocket, que quando instanciada, devemos informar o endereço para o método que assina e migra o protocolo para web sockets. Repare que a instância é criada dentro do evento click do botão Conectar e também já nos associamos aos eventos - autoexplicativos - onopen, onmessage e onclose (e ainda tem o onerror).

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
    <script type="text/javascript">
        var ws;

        $().ready(function ()
        {
            $("#Conectar").click(function () {
                ws = new WebSocket("ws://localhost:2548/api/Publicador/Assinar");

                ws.onopen = function () {
                    $("#Status").text("Conectado. Recuperando Notícias...");
                };

                ws.onmessage = function (evt) {
                    $("#Status").text("");
                    $("#Banner").text(evt.data);
                };

                ws.onclose = function () {
                    $("#Banner").text("");
                    $("#Status").text("Desconectado");
                };
            });

            $("#Desconectar").click(function () {
                ws.close();
            });
        });
    </script>
</head>
<body>
    <input type="button" value="Conectar" id="Conectar" />
    <input type="button" value="Desconectar" id="Desconectar" />
    <br /><br />
    <span id="Status"></span>
    <span id="Banner"></span>
</body>
</html>

Quando acessar a página HTML e clicar no botão Conectar, o resultado é apresentado abaixo. A qualquer momento podemos pressionar no botão Desconectar e indicar ao servidor que não estamos mais interessados no retorno das informações. A cada envio de mensagem do servidor para o cliente, o evento onmessage é disparado, e estamos exibindo a mensagem em um campo na tela.



Apenas para informação, o Fiddler é capaz de identificar quando a conexão é migrada para web sockets, e a partir daí consegue capturar as mensagens que são enviados para o cliente. A imagem abaixo é possível visualizar o log capturado depois da assinatura realizada: