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.

Anúncios

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=”..approotweb.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).

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”&gt;
<head>
    <title></title>
    https://code.jquery.com/jquery-2.1.4.min.js
   
        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();
            });
        });
   
</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:

Protegendo Configurações no ASP.NET

Quando construímos uma aplicação, temos diversos itens para realizar a configuração. Entre estes itens, estão configurações que guiam a execução da aplicação, o gerenciamento de algum recurso que foi incorporado a ela ou simplesmente valores que são necessários para o negócio para qual a mesma está sendo construída.

Entre estas configurações, temos strings de conexões com base de dados, endereços de servidor SMTP para envio de e-mails, chave de identificação para serviços de autenticação (Google, Facebook, etc.), etc. Não é comum manter estes valores em hard-code, pois pode mudar a qualquer momento e variar de ambiente para ambiente, e precisamos ter a flexibilidade de alterar sem a necessidade de recompilar a aplicação.

Isso nos obriga a manter a chave em um arquivo de configuração. Até então trabalhamos com o arquivo chamado web.config, que serve justamente para incluir diversas configurações da aplicação. A nova versão do ASP.NET (5) muda isso, disponibilizando um novo sistema de configuração mais simples e não menos poderoso. Apesar de continuar dando suporte ao formato XML, a versão mais recente adotou como padrão o formato JSON.

O problema desse modelo é a exposição das configurações. Quando trabalhamos com projetos e que podemos expor o repositório do código publicamente (através do GitHub ou qualquer outro gerenciador de código fonte), deixar armazenado neste arquivo informações sigilosas e, consequentemente, torna-las públicas, é algo que nem sempre desejamos. O ASP.NET 5 resolve isso através de um recurso chamado de User Secrets.

Esta funcionalidade permite externalizar certas configurações em um arquivo que fica de fora do controle de versão do código fonte. Sendo assim, para as chaves que são colocadas no arquivo de configuração da aplicação (config.json) e que precisam ser protegidas, podemos recorrer à este recurso, que basicamente consiste em criar um novo arquivo (chamado de secrets.json) e que será armazenado, no Windows, no seguinte endereço: %APPDATA%microsoftUserSecrets<userSecretsId>secrets.json.

É importante dizer que o local deste arquivo varia de acordo com o sistema operacional onde a aplicação está sendo desenvolvida, e não é aconselhável tornar a aplicação dependente dele; futuramente a Microsoft se dá o direito de mudar (como, por exemplo, criptografar o seu conteúdo), e a aplicação corre o risco de parar de funcionar. O <userSecretsId> é substituído pela chave que é colocada no arquivo project.json, e dentro deste diretório teremos o arquivo secrets.json, conforme comentado acima. É importante notar também que precisamos adicionar a dependência para usar este recurso, e para isso, devemos incluir o pacote chamado Microsoft.Framework.Configuration.UserSecrets:

{
  “commands”: {
    “web”: “Microsoft.AspNet.Hosting –config hosting.ini”
  },
  “dependencies”: {
    “Microsoft.AspNet.Server.IIS”: “1.0.0-beta5”,
    “Microsoft.AspNet.Server.WebListener”: “1.0.0-beta5”,
    “Microsoft.Framework.Configuration.UserSecrets”: “1.0.0-beta5”
  },
  “userSecretsId”: “WebApplication1-12345”,
  “version”: “1.0.0-*”,
  “webroot”: “wwwroot”
}

Com essa configuração realizada, podemos adicionar no arquivo config.json todas as configurações necessárias para o funcionamento da aplicação e incluir no arquivo secrets.json somente as configurações que queremos proteger.

[config.json]
{
  “Data”: {
    “DefaultConnection”: {
      “ConnectionString”: “Server=(localdb)\MSSQLLocalDB;Database=Teste;Trusted_Connection=True;”
    }
  },
  “Certificado”:  “123”
}

[secrets.json]
{
  “Certificado”:  “128372478278473248378”
}

Opcionalmente você pode recorrer à uma opção que existe se clicar com o botão direito sobre o projeto e, em seguida, em “Manage User Secrets“, e assim ter acesso ao arquivo secrets.json sem a necessidade de saber onde o Visual Studio está armazenando-o. Mas isso não basta para o ASP.NET entender que ele deve, também, considerar a busca no arquivo secrets.json. Para isso precisamos indicar que à ele que vamos utilizar o recurso de User Secrets, conforme podemos ver abaixo:

public class Startup
{
    public void Configure(IApplicationBuilder app, IApplicationEnvironment appEnv)
    {
        this.Configuration =
            new ConfigurationBuilder(appEnv.ApplicationBasePath)
                .AddJsonFile(“config.json”)
                .AddUserSecrets()
                .Build();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(this.Configuration[“Certificado”]);
        });
    }

    public IConfiguration Configuration { get; set; }
}

A ordem em que invocamos o método é importante, ou seja, neste caso se ele encontrar a chave “Certificado” no arquivo secrets.json o seu valor será apresentado. Caso o valor não exista, então ele extrairá o valor do arquivo config.json.