Dinâmica e ativa, Parte 3: Notificações por push e Serviços Móveis do Windows Azure

Na parte 1 desta série, exploramos o que “dinamismo” significa para um usuário e como os aplicativos participam na criação dessa experiência. Na parte 2, observamos como gravar e depurar serviços Web para oferecer suporte a atualizações periódicas de blocos dinâmicos. Aqui na parte 3, concluiremos essa série com a compreensão de como fornecer atualizações de bloco, notificações do sistema e notificações brutas para dispositivos específicos do cliente em demanda pelo Serviços de Notificação por Push do Windows (WNS), e como os Serviços Móveis do Windows Azure simplificam todo esse processo.

Notificações por push

As atualizações automáticas, como vimos na parte 2, são iniciadas no lado do cliente e fornecem um método de sondagemou “pull” de atualização de um bloco ou notificação. As notificações por “push” ocorrem quando um serviço envia uma atualização diretamente para um dispositivo, onde essa atualização pode ser específica para um usuário, aplicativo e, até mesmo, um bloco secundário.

Diferentemente da sondagem, as notificações por push podem ocorrer a qualquer momento com muito mais frequência, embora você deva estar ciente de que o Windows acelerará a quantidade de tráfego de notificações por push em um dispositivo se ele estiver funcionando com energia de bateria, no modo de espera conectado ou se o tráfego de notificações se tornar excessivo. Isso significa que não há garantias de que todas as notificações serão entregues (principalmente se o dispositivo estiver desligado).

Portanto, evite pensar que pode usar as notificações por push para implementar um bloco de relógio ou outros gadgets de bloco com um tipo de frequência ou resolução de tempo similar. Em vez disso, pense em como você pode usar as notificações por push para conectar blocos e notificações a um serviço de back-end que tenha informações interessantes e significativas a serem passadas para os usuários, o que os encoraja a usar seus aplicativos.

Antes de falarmos sobre os detalhes, observe que existem dois tipos diferentes de notificações por push:

  1. Atualizações XML que contêm atualizações de bloco ou notificação, ou cargas de notificação do sistema: O Windows pode processar essas notificações por push e emitir a atualização ou notificação do sistema em nome de um aplicativo. Um aplicativo pode lidar com essas notificações diretamente, se quiser.
  2. Notificações binárias ou brutas que contêm todos os dados que o sistema deseja enviar: essas notificações devem ser manipuladas por um código específico do aplicativo, já que o Windows não saberá o que fazer com os dados de outra maneira. Consulte Diretrizes e lista de verificação para notificações brutas para obter detalhes, como limites de tamanho (5 KB) e codificação (base64).

Em ambos os casos, um aplicativo executado (em primeiro plano) pode manipular notificações por push diretamente por meio da classe PushNotificationChannel e seu evento PushNotificationReceived. Para cargas XML, o aplicativo pode modificar o conteúdo, alterar marcas e assim por diante, antes de fazer a emissão localmente (ou optar por ignorá-la). Para as notificações brutas, o aplicativo processa o conteúdo e, depois, decide quais notificações emitir, se for o caso.

Se o aplicativo estiver suspenso ou não estiver sendo executado, ele também pode fornecer uma tarefa em segundo plano para essa mesma finalidade. O aplicativo deve solicitar e receber acesso à tela de bloqueio, o que, por sua vez, permite que esse código específico do aplicativo seja executado quando uma notificação é recebida.

Uma tarefa em segundo plano normalmente tem uma ou duas funções quando a notificação é recebida. Em primeiro lugar, ele pode salvar algumas informações relevantes da notificação nos dados locais do aplicativo, onde ele poderá recuperá-los na próxima ativação ou retorno à atividade. Em segundo lugar, a tarefa em segundo plano pode emitir atualizações de bloco local ou notificações do sistema e/ou notificações.

Para compreender melhor as notificações brutas, considere um aplicativo típico de e-mail. Quando seu serviço de back-end detecta novas mensagens para um usuário, ele envia uma notificação bruta por push para WNS que contém vários cabeçalhos de mensagens de email. O serviço faz isso usando um URI de canal que está conectado ao aplicativo específico no dispositivo específico desse usuário.

O WNS, então, tentará enviar essa notificação por push para o cliente. Supondo que isso funcione, o Windows receberá essa notificação e procurará pelo aplicativo associado ao URI de canal mencionado. Se ele não encontrar um aplicativo adequado, a notificação será ignorada. Se um aplicativo existir e estiver funcionando, o Windows acionará o evento PushNotificationReceived, caso contrário, o Windows buscará e invocará uma tarefa em segundo plano para esse aplicativo.

De qualquer maneira, a notificação bruta acaba sendo enviada para algum código de aplicativo, que processa os dados, emite uma atualização de sistema para o bloco do aplicativo a fim de indicar o número de novas mensagens e emite até cinco atualizações de bloco em ciclo com cabeçalhos de mensagem. O aplicativo também pode emitir notificações de sistema para cada nova mensagem que chegar ou, ao menos, para uma que indique que há uma nova mensagem.

Como resultado, as notificações do sistema informam o usuário que o email foi recebido e o bloco do aplicativo na tela inicial fornece uma visualização rápida e imediata da atividade de email.

Para obter mais informações sobre esses manipuladores de evento e tarefas em segundo plano no lado do cliente, consulte amostra de notificações de sistema, a postagem Ser produtivo no segundo plano - tarefas em segundo plano neste blog e o white paper Rede em segundo plano. Para o propósito desta postagem, vamos falar sobre os serviços agora.

Trabalhando com os Serviços de Notificação por Push do Windows (WNS)

Por meio da cooperação do Windows, de aplicativos, serviços e dos WNS, é possível fornecer dados específicos do usuário para um bloco de aplicativo específico (ou manipulador de notificação ou notificação do sistema) em um dispositivo específico para um usuário específico. Veja abaixo as relações entre esses itens.

 

Fluxograma que mostra como o Windows, os aplicativos, serviços e os WNS trabalham juntos para fornecer dados para um bloco de aplicativo específico

 

Sem dúvida, algumas gravações são necessárias para fazer com que eles trabalhem de forma harmoniosa:

  1. Você (desenvolvedor) registra o aplicativo na Windows Store para que ele use notificações por push. Isso gera um SID e segredo do cliente que o serviço usa para autenticar com os WNS (essas informações não devem ser armazenadas nos dispositivos do cliente por motivos de segurança).
  2. No tempo de execução, o aplicativo solicita um URI de canal dos WNS do Windows para cada um de seus blocos dinâmicos (principal e secundário) ou um para notificações brutas. Um aplicativo também deve atualizar esses URIs de canal a cada 30 dias. Para isso, você pode usar outra tarefa em segundo plano.
  3. O serviço do aplicativo fornece um URI por meio do qual ele pode carregar esses URIs de canal juntamente com quaisquer dados que contiverem descrições de seu uso (como local para atualizações de previsão do tempo ou uma conta e atividade de um usuário específico). Ao recebê-los, o serviço armazena esses URIs de canal e dados associados para uso posterior.
  4. O serviço monitora seu back-end em busca de alterações aplicáveis a cada combinação específica de usuário/dispositivo/aplicativo/bloco. Quando o serviço detecta uma condição que aciona uma notificação para um canal específico, ele cria o conteúdo dessa notificação (XML ou bruta), autentica-se com WNS, usando o SID e o segredo do cliente e, depois, envia a notificação para os WNS juntamente com o URI de canal.

Vamos falar detalhadamente sobre cada etapa. (E assim como em um pré-lançamento, se a manipulação de solicitações HTTP deixar você nervoso, os Serviços Móveis do Windows Azure aliviam você de vários detalhes, como mostraremos.)

Registro do aplicativo na Windows Store

Para obter o SID e o segredo do cliente para o seu serviço, consulte Como autenticar com o Serviço de Notificação por Push do Windows (WNS) no Centro de Desenvolvimento do Windows. O SID é o que identifica seu aplicativo com os WNS, e o segredo do cliente é o que o seu serviço usa para informar os WNS que é permitido enviar notificações para o seu aplicativo. Novamente, eles devem ser armazenados no serviço.

Observe que a Etapa 4 em Como autenticar com o Serviço de Notificação por Push do Windows (WNS), “Enviar as credenciais do servidor em nuvem para os WNS”, é algo que você faz apenas quando o serviço envia uma notificação por push. Voltaremos a falar sobre isso mais à frente, porque neste estágio seu serviço ainda não tem a principal informação de que precisa para enviar uma notificação, chamada de URI de canal.

Obtenção e atualização de URIs de canal

O aplicativo do cliente obtém URIs de canal no tempo de execução por meio do objeto Windows.Networking.PushNotifications.PushNotificationChannelManager. Esse objeto tem apenas dois métodos:

  • createPushNotificationChannelForApplicationAsync: cria um URI de canal para o principal bloco do aplicativo, além de notificações de sistema e brutas.
  • createPushNotificationChannelForSecondaryTileAsync: cria um URI de canal para um bloco secundário específico identificado por um argumento tileId.

O resultado das duas operações assíncronas é um objeto PushNotificationChannel. Esse objeto contém o URI de canal em sua propriedade Uri , juntamente com um ExpirationTime para indicar o prazo de atualização desse canal. Um método de Encerrar encerra especificamente o canal, se necessário. E ainda mais importante, é o evento PushNotificationReceived, que é acionado, novamente, quando o aplicativo está em segundo plano e uma notificação por push é recebida por meio desse canal.

O tempo de vida de um URI de canal é 30 dias. Depois disso, os WNS rejeitam todas as solicitações feitas para esse canal. O código do aplicativo, portanto, precisa atualizar esses URIs com os métodos criar acima pelo menos uma vez a cada 30 dias e enviar esses URIs para seus respectivos serviços. Veja um exemplo de boa estratégia:

  • No primeiro lançamento, solicite um URI de canal e salve a cadeia de caracteres na propriedade Uri nos dados locais do aplicativo. Como os URIs de canais são específicos de um dispositivo, não armazene-os nos dados de transferência do aplicativo.
  • Em lançamentos subsequentes, solicite um URI de canal novamente e compare-o ao URI salvo anteriormente. Se eles forem diferentes, envie-os para o serviço ou envie-os e deixe que o serviço substitua um antigo, se necessário.
  • Execute também a etapa anterior no manipulador Resuming do aplicativo (consulte Inicialização, retomada e multitarefa nos documentos), já que é possível que esse aplicativo tenha sido suspenso por mais de 30 dias.
  • Se você estiver preocupado porque o aplicativo não será executado em até 30 dias, implemente uma tarefa em segundo plano com um acionador de manutenção a ser executado com frequência de alguns dias ou de semana em semana. Para obter detalhes, consulte novamente Ser produtivo no segundo plano - tarefas em segundo plano; a tarefa em segundo plano neste caso executará o mesmo código do aplicativo para solicitar um canal ou enviá-lo para o seu serviço.

Envio de URIs de canal para o serviço

Normalmente, os canais de notificação por push funcionam com atualizações específicas do usuário, como status de email, mensagens instantâneas e outras informações personalizadas. Não é comum que seu serviço precise transmitir a mesma notificação para todos os usuários e/ou bloco. Por esse motivo, o serviço precisa associar cada URI de canal a informações mais específicas. Para um aplicativo de email, a ID do usuário é essencial, já que isso especifica a conta a ser verificada. Um aplicativo de previsão de tempo, por outro lado, provavelmente associaria cada URI de canal a uma latitude e longitude para cada bloco (principal e secundário) refletir um local distinto.

O aplicativo, depois disso, deverá incluir esses detalhes ao enviar um URI de canal para seu respectivo serviço, e o serviço deve armazená-lo para uso posterior.

No que se trata da identidade do usuário, é melhor para o aplicativo autenticar o usuário com o serviço separadamente, usando credenciais específicas do serviço ou por meio de um provedor OAuth, como Facebook, Twitter, Google a Conta da Microsoft do usuário (o uso do OAuth é útil com os Serviços Móveis do Windows Azure, como veremos mais à frente). Se, por algum motivo, isso não for possível, certifique-se de criptografar todas as IDs de usuário enviadas para o serviço ou certifique-se de enviá-las por HTTPS.

De qualquer maneira, você decide a forma como quer enviar todas essas informações para o seu serviço (em cabeçalhos, por dados no corpo da mensagem ou como parâmetros no URI do serviço). Esta parte da comunicação ocorre estritamente entre o aplicativo e seu serviço.

Como um exemplo simples, digamos que temos um serviço com uma página chamada receiveuri.aspx (como veremos na próxima seção), o endereço completo dessa página está em uma variável chamada url. O código a seguir solicita um URI de canal principal do Windows para o aplicativo e o publica nessa página pelo HTTP. (Esse código é derivado e simplificado de Exemplos de notificações periódicas e por push do cliente onde a variável itemId , definida em outro local, é usada para identificar blocos secundários; o exemplo também tem uma variável C++, que não é mostrada aqui):

JavaScript:

 

 Windows.Networking.PushNotifications.PushNotificationChannelManager
    .createPushNotificationChannelForApplicationAsync()
    .done(function (channel) {
        //Typically save the channel URI to appdata here.
        WinJS.xhr({ type: "POST", url:url,
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            data: "channelUri=" + encodeURIComponent(channel.uri) 
                + "&itemId=" + encodeURIComponent(itemId)
        }).done(function (request) {
            //Typically update the channel URI in app data here.
        }, function (e) {
            //Error handler
        });
    });

           

C#:

 

 using Windows.Networking.PushNotifications;

PushNotificationChannel channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";
byte[] channelUriInBytes = Encoding.UTF8.GetBytes("ChannelUri=" + WebUtility.UrlEncode(newChannel.Uri) + "&ItemId=" + WebUtility.UrlEncode(itemId));

Task<Stream> requestTask = webRequest.GetRequestStreamAsync();
using (Stream requestStream = requestTask.Result)
{
    requestStream.Write(channelUriInBytes, 0, channelUriInBytes.Length);
}

O código ASP.NET a seguir é uma implementação básica de uma página receiveuri.aspx que processa este HTTP POST, certificando-se de que recebeu um URI de canal, um usuário e algum identificador válido para o item.

Enfatizo a natureza “básica” deste código porque, como você pode ver, a função SaveChannel simplesmente grava o conteúdo da solicitação em um arquivo de texto fixo, que, sem dúvida, não será dimensionado para mais de um usuário! Um serviço real, é claro, usaria um banco de dados para esse propósito, mas a estrutura aqui será semelhante.

 <%@ Page Language="C#" AutoEventWireup="true" %>

<script runat="server">

protected void Page_Load(object sender, EventArgs e)
{
    //Output page header
    Response.Write("<!DOCTYPE html>\n<head>\n<title>Register Channel URI</title>\n</head>\n<html>\n<body>");
    
    //If called with HTTP GET (as from a browser), just show a message.
    if (Request.HttpMethod == "GET")
    {
        Response.StatusCode = 400;
        Response.Write("<p>This page is set up to receive channel URIs from a push notification client app.</p>");
        Response.Write("</body></html>");
        return;
    }

    if (Request.HttpMethod != "POST") {
        Response.StatusCode = 400;
        Response.Status = "400 This page only accepts POSTs.";
        Response.Write("<p>This page only accepts POSTs.</p>");
        Response.Write("</body></html>");        
        return;
    }
    
    //Otherwise assume a POST and check for parameters 
    try
    {
        //channelUri and itemId are the values posted from the Push and Periodic Notifications Sample in the Windows 8 SDK
        if (Request.Params["channelUri"] != null && Request.Params["itemId"] != null)
        {
            // Obtain the values, along with the user string
            string uri = Request.Params["channelUri"];
            string itemId = Request.Params["itemId"];
            string user = Request.Params["LOGON_USER"];
                 
            //TODO: validate the parameters and return 400 if not.
            
            //Output in response
            Response.Write("<p>Saved channel data:</p><p>channelUri = " + uri + "<br/>" + "itemId = " + itemId + "user = " + user + "</p>");

            //The service should save the URI and itemId here, along with any other unique data from the app such as the user;
            SaveChannel(uri, itemId, user);

            Response.Write("</body></html>");
        }
    }
    catch (Exception ex)
    {
        Trace.Write(ex.Message);
        Response.StatusCode = 500;
        Response.StatusDescription = ex.Message; 
        Response.End();
    }
}
</script>

protected void SaveChannel(String uri, String itemId, String user)
{
    //Typically this would be saved to a database of some kind; to keep this demonstration very simple, we'll just use
    //the complete hack of writing the data to a file, paying no heed to overwriting previous data.
    
    //If running in the debugger on localhost, this will save to the project folder
    string saveLocation = Server.MapPath(".") + "\\" + "channeldata_aspx.txt";
 string data = uri + "~" + itemId + "~" + user;

    System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
    byte[] dataBytes = encoding.GetBytes(data);

    using (System.IO.FileStream fs = new System.IO.FileStream(saveLocation, System.IO.FileMode.Create))
    {
        fs.Write(dataBytes, 0, data.Length);
    }

    return;
}

O código deste serviço pode ser encontrado no capítulo 13 do meu livro eletrônico gratuito, Programming Windows 8 Apps in HTML, CSS, and JavaScript (Programando aplicativos do Windows 8 em HTML, CSS e JavaScript) , especificamente no exemplo HelloTiles do conteúdo complementar. Ele foi desenvolvido para funcionar com o exemplo de SDK do lado do cliente mencionado anteriormente. Se você executar o HelloTiles no depurador (Visual Studio 2012 Ultimate ou Visual Studio Express 2012 para Web) com o localhost habilitado, encontrará a URL como https://localhost:52568/HelloTiles/receiveuri.aspx , que você pode colar no exemplo de SDK do lado do cliente. Depois que o exemplo fizer uma solicitação para essa URL, você parará em qualquer ponto de interrupção no serviço para entrar nesse código.

Envio da notificação

No serviço real, você terá algum tipo de andamento que monitora suas fontes de dados e envia notificações por push para usuários específicos, quando apropriado. Isso pode ocorrer de algumas maneiras:

  • Um trabalho agendado pode conferir alertas de previsão do tempo para um local específico, talvez, uma vez a cada 15 a 30 minutos, dependendo da frequência com que esses alertas são emitidos e emitir notificações por push como resposta.
  • Outro serviço, como um back-end de mensagens, pode fazer uma solicitação para uma página no seu servidor quando uma nova mensagem está disponível. Essa página poderá, então, emitir a notificação apropriada.
  • Os usuários podem estar interagindo com páginas da web no seu servidor e suas atividades acionarem notificações por push para canais específicos.

Resumindo, existem vários acionadores de notificações por push, dependendo totalmente da natureza do seu serviço de back-end ou site da web. Para a finalidade deste artigo, o mesmo serviço de exemplo de HelloTiles do capítulo 13 de Programming Windows 8 Apps in HTML, CSS, and JavaScript (Programando aplicativos do Windows 8 em HTML, CSS e JavaScript) tem uma página chamada sendBadgeToWNS.aspx que envia uma notificação por push sempre que você visita essa página em um navegador, usando qualquer URI de canal salvo no arquivo de texto anteriormente. Um serviço real executa algum tipo de pesquisa de banco de dados para obter URIs de canal em vez de ler o conteúdo de um arquivo, mas novamente, a estrutura geral é muito similar.

Página ASP.NET:

 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="sendBadgeToWNS.aspx.cs" Inherits="sendBadgeToWNS" %>

<!DOCTYPE html>

<html xmlns="https://www.w3.org/1999/xhtml">
<head runat="server">    
    <title>Send WNS Update</title>
</head>
<body>
    <p>Sending badge update to WNS</p>    
</body>
</html>

 

C# code-behind:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Net;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;

public partial class sendBadgeToWNS : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        //Load our data that was previously saved. A real service would do a database lookup here
        //with user- or tile-specific criteria.
        string loadLocation = Server.MapPath(".") + "\\" + "channeldata_aspx.txt
 byte[] dataBytes;
        
        using (System.IO.FileStream fs = new System.IO.FileStream(loadLocation, System.IO.FileMode.Open))
        {
            dataBytes = new byte[fs.Length];
            fs.Read(dataBytes, 0, dataBytes.Length);
        }

        if (dataBytes.Length == 0)
        {
            return;
        }
        
        System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();

        string data = encoding.GetString(dataBytes);
        string[] values = data.Split(new Char[] { '~' });
        string uri = values[0]; //Channel URI
        string secret = "9ttsZT0JgHAFveYahK1B6jQbvMOIWYbm";
 string sid = "ms-app://s-1-15-2-2676450768-845737348-110814325-22306146-1119600341-293560589-2707026538";
        
        //Create some simple XML for a badge update
        string xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
        xml += "<badge value='alert'/>";
                    
        PostToWns(secret, sid, uri, xml, "wns/badge");
    }
}

 

Tudo que estamos fazendo aqui é recuperar o URI de canal, criando a carga XML e, depois, autenticando-a com os WNS e fazendo um HTTP POST para esse URI de canal. As últimas duas etapas são feitas na função PostToWns que vem do Início rápido: Tópico Envio de uma notificação por push no Centro de Desenvolvimento do Windows. Estou omitindo uma detalhamento completo porque grande parte dele é a autenticação com WNS pelo OAuth (em https://login.live.com/accesstoken.srf) usando seu segredo de cliente e SID da Windows Store. O resultado dessa autenticação é um token de acesso que será incluído no HTTP POST que enviarmos para o URI de canal:

C#:

 public string PostToWns(string secret, string sid, string uri, string xml, string type = "wns/badge")
{
    try
    {
        // You should cache this access token
        var accessToken = GetAccessToken(secret, sid);

        byte[] contentInBytes = Encoding.UTF8.GetBytes(xml);

        // uri is the channel URI
        HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
        request.Method = "POST";
        request.Headers.Add("X-WNS-Type", type);
        request.Headers.Add("Authorization", String.Format("Bearer {0}", accessToken.AccessToken));

        using (Stream requestStream = request.GetRequestStream())
            requestStream.Write(contentInBytes, 0, contentInBytes.Length);

        using (HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse())
            return webResponse.StatusCode.ToString();
    }
    catch (WebException webException)
    {
        // Implements a maximum retry policy (omitted)
    }
}

Neste exemplo, observe que o cabeçalho X-WNS-Type na solicitação HTTP foi definido para wns/badge e o cabeçalho Content-Type volta, como padrão, para text/xml. Para os blocos, o tipo deve ser wns/tile; as notificações de sistema usam wns/toast. Com as notificações brutas, use o tipo wns/raw e defina o Content-Type para application/octet-stream. Para obter todos os detalhes sobre cabeçalhos, consulte a página Solicitação de serviço e cabeçalhos de notificação por push na documentação.

Falhas de notificações por push

Sem dúvida, o envio de uma solicitação HTTP como esta talvez nem sempre funcione, e há vários motivos pelos quais os WNS responderá com algo diferente de um código 200 (sucesso). Para obter detalhes específicos, consulte a seção “Código de resposta” de Solicitação de serviço e cabeçalhos de notificação por push; veja os erros e causas mais comuns:

  • O URI de canal não é válido (404 Not Found) ou expirado (410 Gone). Nesses casos, o serviço deve remover o URI de canal de seu banco de dados e não fazer mais solicitações a ele.
  • O segredo do cliente e o SID pode não ser válido (401 Unauthorized) ou há uma incompatibilidade entre a ID do pacote do aplicativo em seu manifesto e a ID na Windows Store (403 Forbidden). A melhor maneira de garantir essa compatibilidade é usar o comando de menu Loja > Associar aplicativo à Loja no Visual Studio (este comando é encontrado no menu Projeto no Visual Studio 2012 Ultimate).
  • A carga da notificação bruta é de mais de 5 KB (413 Request Entity Too Large).
  • O cliente poderia estar offline, nesse caso o WNS tentaria novamente de forma automática, mas eventual, relatar uma falha. Para notificações XML, o comportamento padrão é que as notificações por push são armazenadas em cache e entregues quando o cliente volta a ficar online. Para notificações brutas, o armazenamento em cache é desabilitado por padrão; você pode alterar isso configurando o cabeçalho X-WNS-Cache-Policy para cache na solicitação para o URI de canal.

Para outros erros (400 Bad Request), certifique-se de que as cargas XML tenham texto codificado UTF-8 e que as notificações brutas estejam em base64 com o cabeçalho Content-Type definido para application/octet-stream. Também é possível que os WNS estejam acelerando a entrega porque estão, simplesmente, tentando enviar muitas notificações por push em um período específico.

Uma notificação por push bruta também pode ser rejeitada se o aplicativo não estiver presente na tela de bloqueio e o dispositivo estiver no modo de espera conectado. Como o Windows bloqueia notificações brutas para aplicativos sem tela de bloqueio neste estado (e sempre que o aplicativo não estiver em primeiro plano), os WNS têm otimizações para ignorar notificações que sabe que não serão entregues.

Serviços Móveis do Windows Azure

Agora que vimos os detalhes intricate complexos do trabalho com notificações por push, até mesmo omitindo a questão do armazenamento, provavelmente você está se perguntando “Será que não tem como isso ser mais simples?” Imagine o que seria necessário para gerenciar possivelmente centenas de milhões de URIs de canal para uma base de clientes que esperamos que seja grande e em expansão!

Felizmente, você não é o primeiro a fazer essas perguntas. Além das soluções de terceiros, como as soluções oferecidas pela Urban Airship, os Serviços Móveis do Windows Azure podem facilitar muito a sua vida.

Os Serviços Móveis do Windows Azure, que vou abreviar como AMS, fornecem uma solução pré-construída (basicamente vários pontos de extremidade REST) para a maioria dos detalhes de serviços sobre os quais temos conversado. A função de um “serviço móvel” é basicamente gerenciar um banco de dados em seu nome e fornecer funções de biblioteca para enviar cargas facilmente para os WNS. Uma introdução aos AMS pode ser encontrada em Adicione a nuvem ao seu aplicativo com os serviços móveis do Windows Azure.

Para notificações por push, especificamente, obtenha primeiro a biblioteca do lado do cliente no SDK dos Serviços Móveis do Windows Azure para Windows 8. Depois, consulte Introdução às notificações por push nos Serviços Móveis (observe que há uma versão JavaScript do tópico também), que descreve como os AMS ajudam você a atender a todos os requisitos de gravação que vimos anteriormente:

  • Registro do aplicativo na Windows Store: Depois de obter o segredo do cliente e o SID do para o seu aplicativo na Windows Store, salve esses valores na configuração de serviços móveis. Consulte a seção “Registre seu aplicativo para a Windows Store” no tópico Introdução que acabamos de mencionar.
  • Obtenção e atualização de URIs de canal: a solicitação e o gerenciamento de URIs de canal no aplicativo é uma preocupação apenas do cliente, e continuam iguais.
  • Envio de URIs de canal para o serviço: esta etapa fica mais fácil com os AMS. Em primeiro lugar, crie uma tabela de banco de dados no serviço móvel (por meio do portal do Windows Azure). Depois, o aplicativo pode simplesmente inserir registros nessa tabela com URIs de canal e quaisquer outras informações importantes necessárias. A biblioteca do cliente dos AMS lida com a solicitação HTTP nos bastidores e até atualiza o registro do cliente com as alterações feitas no servidor. Além disso, os AMS podem lidar automaticamente com a autenticação por meio da Conta da Microsoft do usuário ou por meio de três outros provedores OAuth, como Facebook, Twitter ou Google, se você tiver registrado seu aplicativo em um deles. Consulte Introdução à autenticação nos Serviços Móveis.
  • Envio da notificação: no serviço móvel, você pode anexar scripts (escritos na variável do JavaScript conhecida como Node.js) às operações de banco de dados, além de criar trabalhos agendados no JavaScript. Nesses scripts, uma simples chamadas para o objeto push.wns com um URI de canal e uma carga gera a solicitação HTTP necessária para o canal. Também é simples capturar falhas de push e registrar a resposta com console.log. Esses registros podem ser facilmente analisados no portal do Windows Azure.

Para obter ainda mais detalhes, consulte estes dois tutoriais de exemplo: Notificações por Push de Bloco, Notificação do Sistema e Notificação com o uso dos Serviços Móveis do Windows Azure and Notificações brutas com o uso dos Serviços Móveis do Windows Azure. Aqui, em vez de repetir todas essas instruções, vamos analisar alguns destaques.

Quando você configura um serviço móvel, ele terá uma URL de serviço específica. É isso que você usa na criação de uma instância do objeto MobileServiceClient no SDK dos AMS:

JavaScript:

 var mobileService = new Microsoft.WindowsAzure.MobileServices.MobileServiceClient(
    "https://{mobile-service-url}.azure-mobile.net/",
    "{mobile-service-key}");

C#:

 using Microsoft.WindowsAzure.MobileServices;

MobileServiceClient MobileService = new MobileServiceClient(
    "https://{mobile-service-url}.azure-mobile.net/",
    "{mobile-service-key}");

C++ (extraído de um exemplo adicional):

 using namespace Microsoft::WindowsAzure::MobileServices;

auto MobileService = ref new MobileServiceClient(
    ref new Uri(L" https://{mobile-service-url}.azure-mobile.net/"), 
    ref new String(L"{mobile-service-key}"));

Essa classe encapsula toda a comunicação HTTP com o serviço, liberando você daquelas estruturas de baixo nível em que você não gosta nem de pensar.

Para autenticar com um provedor OAuth específico, use o login ou o método LoginAsync. O resultado disso é um objeto do Usuário que fornece essas informações para o aplicativo. (Depois de autenticada, a propriedade CurrentUser do objeto do cliente também terá a id de usuário.) Quando você autentica o serviço móvel diretamente dessa maneira, o serviço terá acesso à id do usuário e não haverá necessidade de o cliente enviá-la explicitamente:

JavaScript:

 mobileService.login("facebook").done(function (user) { /* ... */ });
mobileService.login("twitter").done(function (user) { /* ... */ });
mobileService.login("google").done(function (user) { /* ... */ });
mobileService.login("microsoftaccount").done(function (user) { /* ... */ });

C#:

 MobileServiceUser user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
MobileServiceUser user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.Twitter);
MobileServiceUser user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.Google);
MobileServiceUser user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);

C++:

 task<MobileServiceUser^> (MobileService->LoginAsync(MobileServiceAuthenticationProvider::Facebook))
    .then([this](MobileServiceUser^ user) { /* */ } );
task<MobileServiceUser^> (MobileService->LoginAsync(MobileServiceAuthenticationProvider::Twitter))
    .then([this](MobileServiceUser^ user) { /* */ } );
task<MobileServiceUser^> (MobileService->LoginAsync(MobileServiceAuthenticationProvider::Google))
    .then([this](MobileServiceUser^ user) { /* */ } );
task<MobileServiceUser^> (MobileService->LoginAsync(MobileServiceAuthenticationProvider::MicrosoftAccount))
    .then([this](MobileServiceUser^ user) { /* */ } );

O envio de um URI de canal para o serviço é, novamente, apenas o armazenamento de um registro no banco de dados do serviço, e o objeto do cliente faz as solicitações HTTP. Para fazer isso, basta solicitar o objeto do banco de dados e inserir o registro, como mostrado nos exemplos abaixo do exemplo de tutorial da notificação por push informado por link anteriormente. Em cada trecho de código, suponha que ch contém o objeto PushNotificationChannel das APIs do WinRT. Também é possível incluir quaisquer outras propriedades no objeto de canal criado, como uma id de bloco secundário ou outros dados que identifiquem o propósito do canal.

JavaScript:

 var channelTable = MobileServicesSample.mobileService.getTable('Channels');

var channel = {
    uri: ch.uri, 
    expirationTime: ch.expirationTime.
};

channelTable.insert(channel).done(function (item) {

    },
    function () {
        // Error on the insertion.
    });
}

C#:

 var channel = new Channel { Uri = ch.Uri, ExpirationTime = ch.ExpirationTime };
var channelTable = privateClient.GetTable<Channel>();

if (ApplicationData.Current.LocalSettings.Values["ChannelId"] == null)
{
    // Use try/catch block here to handle exceptions
    await channelTable.InsertAsync(channel);
}

C++:

 auto channel = ref new JsonObject();
channel->Insert(L"Uri", JsonValue::CreateStringValue(ch->Uri));
channel->Insert(L"ExpirationTime", JsonValue::CreateBooleanValue(ch->ExpirationTime));

auto table = MobileService->GetTable("Channel");
task<IJsonValue^> (table->InsertAsync(channel))
    .then([this, item](IJsonValue^ V) { /* ... */ });

Observe que, uma vez que o registro do canal foi inserido, quaisquer alterações ou adições que o serviço possa ter feito a esse registro serão refletidas no cliente.

Além disso, se você errar a ortografia do nome do banco de dados na chamada GetTable/getTable, você não verá nenhuma exceção até que tente inserir um código. Isso pode ser um bug difícil de ser encontrado, portanto, se você tiver certeza de que tudo deveria estar funcionando, mas não está, confira o nome do banco de dados.

Agora, mais uma vez, essas inserções no lado do cliente são traduzidas em solicitações HTTP para o serviço, mas mesmo no lado do serviço, isso também fica oculto para você. Em vez de receber e processar as solicitações, você anexa scripts personalizados a cada operação de banco de dados (inserir, ler, atualizar e excluir).

Esses scripts são escritos como funções JavaScript com o uso dos mesmos objetos e métodos intrínsecos disponíveis em Node.js (nenhum deles está relacionado a nenhum JavaScript do lado do cliente no aplicativo). Cada função recebe parâmetros apropriados: insert e update recebe o novo registro, delete recebe a id do item e read recebe uma consulta. Todas as funções também recebem um objeto user, se o aplicativo tiver autenticado o usuário com o serviço móvel e um objeto request que permite que você execute a operação e gere o que se torna a resposta.

O script insert mais simples (e padrão) apenas executa a solicitação e insere o registro, item, da forma como ele se apresenta:

 function insert(item, user, request) {
    request.execute();
}

Se você quiser anexar um carimbo de data/hora e a id do usuário ao registro, isso é tão fácil quanto adicionar essas propriedades ao parâmetro item antes de executar a solicitação:

 function insert(item, user, request) {
    item.user = user.userId;
    item.createdAt = new Date();
    request.execute();
}

Observe que as alterações feitas ao item nesses scripts antes da inserção no banco de dados são automaticamente propagadas de volta para o cliente. No código acima, o objeto channel no cliente conteria as propriedades user e createdAt depois do êxito da inserção. Muito conveniente!

Os scripts do serviço também pode executar ações adicionais depois de request.execute, principalmente em resposta ao êxito ou à falha, mas deixarei esses detalhes para Documentação de como fazer do exemplo de script de servidor.

Para voltar agora para as notificações por push, salvar URIs de canal em uma tabela é apenas uma parte da equação, e o serviço pode ou não enviar notificações em resposta a esse evento específico. É mais provável que o serviço tenha outras tabelas com informações adicionais, onde as operações executadas nessas tabelas acionam notificações para algum subconjunto de URIs de canal. Discutiremos alguns exemplos na próxima seção. Qualquer que seja o caso, você envia uma notificação por push de um script usando o objeto push.wns. Isso, mais uma vez, apresenta muitos métodos para enviar tipos específicos de atualizações (inclusive brutas), em que blocos, notificações do sistema e notificações trabalham diretamente por meio de métodos de nomes correspondentes aos modelos disponíveis. Por exemplo:

 push.wns.sendTileSquarePeekImageAndText02(channel.uri, {
    image1src: baseImageUrl + "image1.png",
    text1: "Notification Received",
    text2: item.text
}, {
    success: function (pushResponse) {
        console.log("Sent Tile Square:", pushResponse);
    },
    error: function (err) {
        console.log("Error sending tile:", err);
    }

});

push.wns.sendToastImageAndText02(channel.uri, {
    image1src: baseImageUrl + "image2.png",
    text1: "Notification Received",
    text2: item.text
}, {
    success: function (pushResponse) {
        console.log("Sent Toast:", pushResponse);
    }
});

push.wns.sendBadge(channel.uri, {
    value: value,
    text1: "Notification Received"
}, {
    success: function (pushResponse) {
        console.log("Sent Badge:", pushResponse);
    }
});

Novamente, a função console.log cria uma entrada nos registros que pode ser visualizada no portal dos Serviços Móveis do Azure, e normalmente, você deve incluir chamadas de registros em manipuladores de erro, como mostrado na notificação de bloco acima.

Talvez você já tenha reparado que os métodos de envio* estão vinculados a um modelo específico e para os blocos, isso significa que cargas grandes e quadradas devem ser enviadas separadamente, como duas notificações. Lembre-se de que você quase sempre quer enviar os dois tamanhos juntos porque o usuário controla como o bloco será exibido em sua tela inicial. Com as funções específicas de envio de funções de push.wns, então, isso significa que, ao fazer duas chamadas sequenciais, cada uma delas gerará uma notificação por push separada.

Para combinar várias atualizações, que inclui o envio de várias atualizações de bloco com marcas diferentes de uma só vez ou o envio de várias notificações do sistema, use os métodos push.wns.sendTile e push.wns.sendToast. Por exemplo:

 var channel = '{channel_url}';

push.wns.sendTile(channel,
    { type: 'TileSquareText04', text1: 'Hello' },
    { type: 'TileWideText09', text1: 'Hello', text2: 'How are you?' },
    { client_id: '{your Package Security Identifier}', client_secret: '{your Client Secret}' },

    function (error, result) {
        // ...
    });

Em um nível ainda mais inferior, o push.wns.send permite que você seja muito exato com o conteúdo da notificação; o push.wns.sendRaw também está presente para notificações. Para obter todos os detalhes, consulte novamente a documentação do objeto push.wns.

Cenários da vida real com os Serviços Móveis do Windows Azure

O aplicativo de exemplo em Notificações por Push de Bloco, Notificação do Sistema e Notificação com o uso dos Serviços Móveis do Windows Azure mostra como enviar notificações por push em resposta a uma nova mensagem inserida na tabela do banco de dados. No entanto, isso significa que o aplicativo acaba enviando uma notificação por push para si mesmo, o que não seria normalmente necessário (exceto, talvez, para enviar notificações por push para o mesmo aplicativo em outros dispositivos do cliente).

O que é mais provável acontecer é o serviço enviar notificações por push em resposta aos eventos que ocorrem fora do aplicativo/bloco que, por fim, recebem essas notificações. Considere alguns cenários:

  • Uso de redes sociais:

Os aplicativos podem empregar uma rede social do usuário para implementar recursos, como emitir desafios para os amigos de alguém. Por exemplo, quando uma pessoa faz uma alta pontuação em um jogo, talvez você queira enviar desafios por meio de atualizações de blocos ou notificações do sistema para os amigos do usuário que também têm esse jogo instalado. O mesmo pode acontecer em aplicativos de atividades físicas, onde você pode postar um novo melhor horário para um determinado tipo de atividade.

Para fazer isso, o aplicativo pode inserir um novo registro em uma tabela de serviço móvel apropriada (Scores, BestTimes etc.). No script de inserção, o serviço consulta seu banco de dados em busca de amigos apropriados do usuário atual e, depois, envia notificações para esses URIs de canal. Critérios de consulta adicionais descrevem o aspecto exato de um jogo, o tipo específico de exercício (para blocos secundários) e assim por diante.

  • Atualizações e alertas de previsão do tempo:

Os aplicativos de previsão do tempo normalmente permitem que você atribua um local para o bloco principal do aplicativo e crie blocos secundários para locais adicionais. As informações importantes com cada bloco são a latitude e a longitude do local, que o aplicativo envia para o serviço juntamente com cada URI de canal (inserindo essas informações em uma tabela). Para acionar uma atualização para esse canal, o serviço pode ter outro processo (como um trabalho agendado descrito abaixo) que consulte periodicamente um serviço de previsão do tempo central para atualizações de alertas e, depois, processe essas respostas e insira as mensagens apropriadas na tabela de alertas no serviço móvel. O script de inserção, depois disso, recupera os URIs de canal apropriados e envia as atualizações. Como alternativa, se um serviço de previsão do tempo permite que você o registre para alertas ou atualizações periódicas, outra página no serviço receberia essas solicitações (HTTP PUTs, provavelmente), processaria-as e invocaria o serviço móvel para inserir um registro, acionando, assim, uma atualização.

  • Mensagens:

A manipulação de mensagens instantâneas acontece, muitas vezes, da mesma maneira que o recebimento de atualizações de previsão do tempo. Novamente, outro processo monitora o recebimento de novas mensagens, como uma mensagem que verifica as mensagens de entrada periodicamente ou se registra na fonte da mensagem para receber alertas quando novas mensagens são recebidas. Em ambos os casos, a chegada de uma nova mensagem aciona notificações por push para os canais apropriados. Nesse caso, o URIs de canal está associado ao bloco de um usuário para um tipo específico de mensagem. Provavelmente, você usaria notificações brutas nesse caso, já que elas são semelhantes ao cenário de e-mail descrito nesta postagem.

Em todos esses cenários, observe que não é realmente necessário adicionar nada em uma tabela de banco de dados. Se você não chama request.execute no script de inserção, nada é enviado para o banco de dados e, ainda assim, você poderia executar outras tarefas nesse script, como o envio de notificações. Ou seja, não há necessidade de encher um banco de dados com registros que você não vai usar posteriormente, principalmente porque o armazenamento de dados gera custos.

Observe também que os Serviços Móveis do Azure têm facilidade para agendar trabalhos. Você pode ler sobre isso em Schedule recurring jobs in Mobile Services (Agendar tarefas recorrentes nos Serviços Móveis). Esses trabalhos podem recuperar, rotineiramente, dados de outros serviços, processá-los e inserir registros no banco de dados do serviço, novamente acionando notificações por push. Da mesma maneira, com mostramos na Parte 2 desta série, outras páginas da web e aplicativos móveis também podem fazer alterações a essa banco de dados e acionar notificações por push. Os scripts de banco de dados no serviço móvel serão executados em todos esses casos.

Resumo

Para encerrar esta série, então, exploramos o escopo todo do que “dinâmico e ativo” significa para os usuários, desenvolvedores, aplicativos e serviços. Vimos as funcionalidades do bloco, das notificações e das atualizações de notificações do sistema, como configurar notificações periódicas, como usar tarefas em segundo plano como parte deste processo e a estrutura dos serviços para lidar manipular notificações por push e periódicas. E, sem dúvida, os Serviços Móveis do Windows Azure fornecem um nível muito mais alto de visualização de todo esse processo de trabalho com notificações por push. Com certeza ele ajuda você a ser muito mais produtivo — e evita muita dor de cabeça, provavelmente! — do que seria criando novos serviços do zero.

Kraig Brockschmidt
Gerente de programas, equipe de ecossistema do Windows
Autor, Programming Windows 8 Apps in HTML, CSS, and JavaScript (Programando aplicativos do Windows 8 em HTML, CSS e JavaScript)