Agosto de 2017

Volume 32 - Número 8

Microsoft Office - Mensagens Acionáveis do Outlook

Por Woon Kiat Kiat | Agosto de 2017

Adoro emails. É no meu local de trabalho que me informo sobre o que está acontecendo e o que preciso fazer. É lá que recebo notificações sobre novos relatórios de despesas enviados pela equipe, novas respostas para meus tweets, novos comentários sobre minhas solicitações de pull, etc. Mas o email poderia ser muito melhor. Por que preciso clicar em um link na mensagem e esperar o site do sistema financeiro ser carregado em um navegador para aprovar um relatório de despesas? Por que preciso mudar de cenário? Eu deveria poder aprovar o relatório de despesas diretamente no cliente email.

Parece familiar? O Outlook está prestes a facilitar muito a sua vida, a economizar seu tempo e a aumentar sua produtividade.

Apresentando as Mensagens Acionáveis

As Mensagens Acionáveis permitem que usuários concluam tarefas diretamente no cliente de email. Ele oferece uma experiência nativa no cliente Outlook para desktop e no Outlook Web Access (OWA). Neste artigo, usarei Outlook para me referir ao cliente para desktop e ao OWA.

No exemplo que usarei aqui, a empresa fictícia Contoso tem um sistema interno de aprovação de despesas. Sempre que um funcionário envia um relatório de despesa, o gerente recebe uma mensagem de email com uma solicitação de aprovação. Mostrarei todas o passo a passo de como usar as Mensagens Acionáveis no Outlook, que permitem ao gerente aprovar a solicitação na própria mensagem de email.

Minha primeira Mensagem Acionável

A Figura 1 mostra o código HTML de uma Mensagem Acionável. Ela pode parecer complicada, mas, acredite, não é. Explicarei detalhadamente a marcação nas próximas seções. O primeiro passo é enviar um email com a marcação mostrada na Figura 1 para sua conta de email do Office 365.

Figura 1: Código HTML de uma Mensagem Acionável do Outlook

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf8">
    <script type="application/ld+json">{
      "@context": "http://schema.org/extensions",
      "@type": "MessageCard",
      "hideOriginalBody": "true",
      "title": "Expense report is pending your approval",
      "sections": [{
        "text": "Please review the expense report below.",
        "facts": [{
          "name": "ID",
          "value": "98432019"
        }, {
          "name": "Amount",
          "value": "83.27 USD"
        }, {
          "name": "Submitter",
          "value": "Kathrine Joseph"
        }, {
          "name": "Description",
          "value": "Dinner with client"
        }]
      }],
      "potentialAction": [{
        "@type": "HttpPost",
        "name": "Approve",
        "target": ""
      }, {
        "@type": "OpenUri",
        "name": "View Expense",
        "targets": [ { "os": "default", 
        "uri": "https://expense.contoso.com/view?id=98432019"} ]
      }]
    }
    </script>
  </head>
  <body>
    <p>Please <a href="https://expense.contoso.com/view?id=98432019">approve</a> 
      expense report #98432019 for $83.27.</p>
  </body>
</html>

Como mostra a Figura 2, no corpo do email há um cartão de mensagem com dois botões interativos. Se você clicar no botão Aprovar, isso retornará um erro, porque a URL da ação ainda não foi especificada. Você adicionará essa URL depois. Se você clicar no botão Exibir Despesa, o navegador será aberto no site Aprovação de Despesa.

Mensagem Acionável no Outlook Web Access
Figura 2: Mensagem Acionável no Outlook Web Access

Marcação MessageCard

A mensagem do email é uma marcação HTML típica. Para transformá-la em uma Mensagem Acionável no Outlook, você precisa inserir a marcação MessageCard no elemento <script>. Uma das principais vantagens dessa abordagem é que as mensagens de email continuarão sendo processadas normalmente em clientes que não reconhecem a marcação MessageCard. O formato dessa marcação se chama JSON-LD, que é um formato padrão para criar dados legíveis por máquina na Internet. Agora, analisaremos a marcação detalhadamente. Estas duas linhas de código são obrigatórias em todas as marcações:

"@context": "http://schema.org/extensions",
"@type": "MessageCard",

Você define o contexto como http://schema.org/extensions e o tipo como "MessageCard". O tipo MessageCard indica que esse email é uma Mensagem Acionável.

Em seguida, vem a propriedade "hideOriginalBody". Quando o valor dessa propriedade estiver definido como true, o corpo do email será ocultado, e apenas o cartão será exibido, como mostra a Figura 2. Isso é útil se o cartão contiver todas as informações de que o usuário precisa, ou se o conteúdo do cartão for o mesmo do corpo do email. Caso a mensagem seja visualizada em um cliente de email que não reconheça cartões de mensagem, a mensagem original do email será exibida, e o cartão não, independentemente do valor de "hideOriginalBody." O valor da propriedade "title" refere-se ao título de MessageCard:

"hideOriginalBody": "true",
"title": "Expense report is pending your approval",

Em seguida, vem "sections". Você pode entender uma seção como algo que representa uma "atividade". Se seu cartão tiver várias atividades, você deve incluir uma seção para cada atividade. A Figura 3 mostra a marcação com uma seção. Usamos a propriedade facts de uma seção, que é uma matriz com os pares nome-valor, para exibir os detalhes de um relatório de despesas.

Figura 3: Cartão com uma seção

"sections": [{
  "text": "Please review the expense report below.",
  "facts": [{
    "name": "ID",
    "value": "98432019"
  }, {
    "name": "Amount",
    "value": "83.27 USD"
  }, {
    "name": "Submitter",
    "value": "Jonathan Kiev"
  }, {
    "name": "Description",
    "value": "Dinner with client"
  }]
}],

Em seguida, vem "potentialAction". Esse elemento é uma matriz de ações que podem ser chamadas com esse cartão. No momento, as ações permitidas são OpenUri e HttpPOST:

"potentialAction": [{
  "@type": "HttpPost",
  "name": "Approve",
  "target": ""
}, {
  "@type": "OpenUri",
  "name": "View Expense",
  "targets": [ { "os": "default",
  "uri": "https://expense.contoso.com/view?id=98432019"} ]
}]

A ação OpenUri abrirá um navegador e a URL especificada na propriedade targets. A propriedade targets é uma matriz que permite especificar URLs de uma determinada plataforma. Por exemplo, você quer que os usuários que utilizam as plataformas iOS e Android sejam direcionados para URLs diferentes. Neste exemplo, o sistema operacional (os) está definido como padrão (default), logo a URL é a mesma para todas as plataformas.

A ação HttpPOST fará uma solicitação HTTP POST para um sistema Web externo, que deve ser especificado na propriedade target. No código de exemplo que estou usando, nenhum valor foi definido. É por isso que você recebe uma mensagem de erro quando pressiona o botão Aprovar.

Aplicativo MessageCard Playground

Seria ótimo, se você pudesse visualizar a aparência do cartão enquanto cria a marcação. A Microsoft tem um aplicativo Web para isso. Ele se chama MessageCard Playground App (bit.ly/2s274S9).

Sempre crie seu cartão primeiro nesse aplicativo. Quando estiver satisfeito com o layout do cartão, então você poderá usar a marcação em suas mensagens de email.

Chamando um serviço Web externo com a ação HttpPOST

Agora você tem um cartão de mensagem com duas ações. OpenUri abrirá um navegador e a URL especificada na ação. Para a ação HttpPOST, chame a API REST que aprovará o relatório de despesas. Substitua a ação HttpPOST pelo seguinte:

{
  "@type": "HttpPost",
  "name": "Approve",
  "target": "https://api.contoso.com/expense/approve",
  "body": "{ \"id\": \"98432019\" }"
}

Quando um usuário clicar no botão Aprovar, um servidor da Microsoft fará uma solicitação HTTP POST semelhante à seguinte:

POST api.contoso.com/expense/approve
Content-Type: application/json

{ "id": "98432019" }

A propriedade target indica a URL, para a qual o servidor da Microsoft fará a solicitação POST, e a propriedade body indica o conteúdo da solicitação. Considera-se que o conteúdo de body sempre estará no formato JSON.

Agora, envie um email com a nova marcação para sua caixa de entrada. Quando você clica no botão Aprovar, a ação é executada com êxito.

Ação ActionCard

Agora vamos adicionar o botão Rejeitar, para que o gerente possa rejeitar um relatório de despesa. Para o botão Rejeitar, você precisará incluir o motivo da rejeição do relatório de despesas.

A ação ActionCard foi criada para casos como esse. Ela contém uma ou mais entradas e ações associadas, que podem ser OpenUri ou HttpPost. A ação ActionCard deve ser inserida entre HttpPOST e OpenUri, como mostra a Figura 4.

Figura 4: Ação ActionCard

"potentialAction": [{
  "@type": "HttpPost",
  ...
}, {
  "@type": "ActionCard",
  "name": "Reject",
  "inputs": [{
    "@type": "TextInput",
    "id": "comment",
    "isMultiline": true,
    "title": "Explain why the expense report is rejected"
  }],
  "actions": [{
    "@type": "HttpPOST",
    "name": "Reject",
    "target": "https://api.contoso.com/expense/reject",
    "body": "{ \"id\": \"98432019\", \"comment\": \"{{rejectComment.value}}\" }"
  }]
},{
  "@type": "OpenUri",
  ...
}]

Se você enviar a marcação atualizada para sua caixa de entrada, poderá ver três botões: Aprovar, Rejeitar e Exibir despesa. Se clicar no botão Rejeitar, você poderá adicionar comentários antes de rejeitar o relatório de despesas.

Analisaremos a marcação da ação ActionCard agora. Além das propriedades type e name, ela também tem uma matriz de entradas e ações. Neste exemplo, o elemento TextInput, que está definido como Multiline, permite que os usuários insiram texto. Outras opções que permitem a inserção de texto são: DateInput e Multichoice­Input. Para obter mais informações, consulte bit.ly/2t3bLJN.

Há uma ação HttpPOST que chamará o serviço Web externo para rejeitar o relatório de despesas. Ela é semelhante à ação HttpPOST do botão Aprovar. Uma das principais diferenças entre elas é que você deseja transmitir os comentários inseridos pelos usuários para a chamada do serviço Web. Você pode referenciar o valor da entrada de texto usando {{rejectComment.value}}, onde rejectComment é a ID da entrada de texto.

Serviço Web para Mensagens Acionáveis

Até agora, você viu a marcação para Mensagens Acionáveis no Outlook e como ela funciona. No restante deste artigo, descreverei como um serviço Web processaria solicitações enviada pelas Mensagens Acionáveis no Outlook.

As Mensagens Acionáveis funcionarão com qualquer serviço Web capaz de processar solicitações HTTP POST. Neste exemplo, seu serviço Web é um controlador de API no ASP.NET MVC. A Figura 5 mostra o controlador de API.

Figura 5: Controlador da API de despesas

[RoutePrefix("expense")]
public class ExpenseController : ApiController
{
  [HttpPost]
  [Route("approve")]
  public HttpResponseMessage Approve([FromBody]JObject jBody)
  {
    string expenseId = jBody["id"].ToString();

    // Process and approve the expense report.
    HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK);
    response.Headers.Add("CARD-ACTION-STATUS", "The expense was approved.");

      return response;    
  }

  [HttpPost]
  [Route("reject")]
  public HttpResponseMessage Reject([FromBody]JObject jBody)
  {
    string expenseId = jBody["id"].ToString();
    string comment = jBody["comment"].ToString();

    // Process and reject the expense report.
    HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK);
    response.Headers.Add("CARD-ACTION-STATUS", "The expense was rejected.");

    return response;    
  }
}

Existem dois métodos neste controlador de API: um para aprovação e outro para rejeição. O serviço Web deve retornar um código de status HTTP de 2xx para a ação ser considerada bem sucedida. O serviço Web também pode incluir o cabeçalho CARD-ACTION-STATUS na resposta. O valor desse cabeçalho será exibido para o usuário em uma área reservada do cartão. Se você implantar o serviço Web para https://api.contoso.com e clicar no botão Aprovar, uma notificação de que a operação foi concluída com sucesso será exibida, como mostra a Figura 6.

Relatório de despesas com a notificação de aprovação bem-sucedida
Figura 6: Relatório de despesas com a notificação de aprovação bem-sucedida

Agora sua Mensagem Acionável está completa. Você pode enviar a Mensagem Acionável e, quando o usuário clicar no botão Aprovar, uma solicitação HTTP POST será enviada para seu serviço Web. Seu serviço Web processará a solicitação e retornará 200 OK. Então, o Outlook marcará a ação como concluída. Agora mostrarei como proteger seu serviço Web.

Tokens de finalidade limitada

Como a ID da despesa sempre segue um determinado formato, um invasor pode realizar um ataque publicando várias solicitações com IDs de despesa diferentes. Se o invasor descobrir a ID de uma despesa, ele conseguirá aprovar ou rejeitar o relatório de despesas correspondente. A Microsoft recomenda que desenvolvedores usem “tokens de finalidade limitada” como parte da URL da ação target ou no corpo da solicitação. Esse token de finalidade limitada deve ser difícil de ser descoberto por invasores. Por exemplo, eu uso uma GUID, um número de 128 bits como o token de finalidade limitada. Esse token pode ser usado para correlacionar URLs de serviço com seus usuários e solicitações específicos. Ele também pode ser usado para proteger serviços Web contra ataques de replay (bit.ly/2sBQmdn). Atualize a marcação para incluir uma GUID em body:

{
  "@type": "HttpPost",
  "name": "Approve",
  "target": "https://api.contoso.com/expense/approve",
  "body": "{ \"id\": \"98432019\", \"token\": \
  "d8a0bf4f-ae70-4df6-b129-5999b41f4b7f\" }"
}

Token de portador

Embora os tokens de finalidade limitada dificultem o envio de solicitações forjadas por invasores, eles não são perfeitos. Idealmente, um serviço Web deveria ser capaz de identificar se uma solicitação HTTP POST veio de um servidor da Microsoft ou de algum servidor não autorizado e possivelmente malicioso.

A Microsoft resolve esse problema incluindo um token de portador em cada solicitação HTTP POST que envia para serviços Web. O token de portador é um Token Web JSON (JWT) e integra o cabeçalho Authorization de uma solicitação. Quando um usuário clica no botão Aprovar, o serviço Web recebe uma solicitação como esta:

POST https://api.contoso.com/expenses/approve

Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJ­SUzI1NiIsIng1dCI6I­jhx­Z3A4­VER­CbDJINkp5­RkU0WjM0­ZDJoYS1rR­SIsImtpZCI6I­jhxZ3A4V­ERCbDJINkp5RkU0WjM0ZD­JoYS1rRSJ9.eyJpYXQiOjE0ODQwODkyNzksInZlciI6IlNUSS5FeHRlcm­5hbEFjY2Vzc1Rva2­VuLlYxIiwiYXBw aWQiOiI0OGFmMD­hkYy1mN­mQyLTQzNWYtYjJhNy0wN­jlhYmQ5OWMwODYiLCJzd­WIiOiJk­YXZpZEBj­ b250b3NvLmN­vbSIsImFwcGlk­YWNyIjoiMiIsIm­FjciI6IjAi­LCJzZW5kZ­XIiOiJleHB­lbnNlYXBw... (truncated for brevity)

{
  "id": "98432019",
  "token": "d8a0bf4f-ae70-4df6-b129-5999b41f4b7f"
}

A string codificada de base 64 que vem depois de "Bearer" no cabeçalho Authorization é um Token Web JSON (JWT). Você pode decodificar o JWT em jwt.calebb.net. A Figura 7 mostra um token de exemplo decodificado.

Figura 7: Exemplo de Token Web JSON (JWT) de portador

{
  typ: "JWT",
  alg: "RS256",
  x5t: "8qgp8TDBl2H6JyFE4Z34d2ha-kE",
  kid: "8qgp8TDBl2H6JyFE4Z34d2ha-kE"
}.
{
  iat: 1484089279,
  ver: "STI.ExternalAccessToken.V1",
  appid: "48af08dc-f6d2-435f-b2a7-069abd99c086",
  sub: "david@contoso.com",
  appidacr: "2",
  acr: "0",
  sender: "expenseapproval@contoso.com",
  iss: "https://substrate.office.com/sts/",
  aud: "https://api.contoso.com",
  exp: 1484090179,
  nbf: 1484089279
}.
[signature]

Cada JWT tem três segmentos separados por um ponto(.). O primeiro segmento é o cabeçalho, que descreve as operações criptográficas aplicadas ao JWT. Neste caso, o algoritmo (alg) utilizado para assinar o token é RS256, que significa RSA usando o algoritmo de hash SHA-256. O valor de x5t especifica a impressão digital da chave usada para assinar o token.

O segundo segmento é o conteúdo do cartão. Ele contém uma lista de declarações fornecidas pelo token. Os serviços Web devem usar essas declarações para verificar uma solicitação. A tabela na Figura 8 descreve essas declarações.

Figura 8: Descrição das declarações no conteúdo

Declarações Descrição
iss O emissor do token. Seu valor deve ser sempre https://substrate.office.om/sts/. O serviço Web deve rejeitar o token e a solicitação se o valor não for esse.
appid A ID do aplicativo que emite o token. Seu valor deve ser sempre 48af08dc-f6d2-435f-b2a7-069abd99c086. O serviço Web deve rejeitar o token e a solicitação se o valor não for esse.
aud O público alvo do token. Seu valor deve ser o nome do host da URL do serviço Web. O serviço Web deve rejeitar o token e a solicitação se o valor não for esse.
sub O usuário que executou a ação. Seu valor deve ser o endereço de email da pessoa que executou a ação, se esse endereço de email ou qualquer um dos endereços de email do proxy estiverem no campo Para: da mensagem. Se nenhum dos endereços de email corresponder, o valor com hash do nome UPN do usuário será usado. É garantido ter o mesmo valor com hash para o mesmo nome UPN.
sender O endereço de email do remetente da mensagem original.
tid A ID de locatário do emissor do token.

O terceiro segmento é a assinatura digital do token. Ao verificar a assinatura, os serviços Web podem ter certeza de que o token foi enviado pela Microsoft e confiar nas declarações do token.

A verificação de uma assinatura digital é uma tarefa complexa. Felizmente, existe uma biblioteca no NuGet que facilita essa tarefa de verificação. Essa biblioteca está disponível em bit.ly/2stq90c e foi criada pela Microsoft. A Microsoft também publicou códigos de exemplo de verificação de token em outras linguagens. Os links para esses códigos de exemplos podem ser encontrados ao final deste artigo.

Depois de incluir o pacote NuGet no projeto do serviço Web, você poderá usar o método VerifyBearerToken, como mostra a Figura 9, para verificar o token de portador em uma solicitação.

Figura 9: O método VerifyBearerToken

private async Task<HttpStatusCode> VerifyBearerToken(
  HttpRequestMessage request, string serviceBaseUrl, string expectedSender)
{
  if (request.Headers.Authorization == null ||
    !string.Equals(request.Headers.Authorization.Scheme, "bearer", 
      StringComparison.OrdinalIgnoreCase) ||
      string.IsNullOrEmpty(request.Headers.Authorization.Parameter))
  {
    return HttpStatusCode.Unauthorized ;
  }

  string bearerToken = request.Headers.Authorization.Parameter;
  ActionableMessageTokenValidator validator = 
    new ActionableMessageTokenValidator();
  ActionableMessageTokenValidationResult result = 
    await validator.ValidateTokenAsync(bearerToken, serviceBaseUrl);

  if (!result.ValidationSucceeded)
  {
    return HttpStatusCode.Unauthorized;
  }

  if (!string.Equals(result.Sender, expectedSender, 
    StringComparison.OrdinalIgnoreCase) ||
      !result.ActionPerformer.EndsWith("@contoso.com", 
        StringComparison.OrdinalIgnoreCase))
  {
    return HttpStatusCode.Forbidden;
  }

  return HttpStatusCode.OK;
}

[HttpPost]
[Route("approve")]
public async Task<HttpResponseMessage> Approve([FromBody]JObject jBody)
{
  HttpRequestMessage request = this.ActionContext.Request;
  HttpStatusCode result = await VerifyBearerToken(
    request, "https://api.contoso.com", 
    "expenseapproval@contoso.com");

  switch (result)
  {
    case HttpStatusCode.Unauthorized:
      return request.CreateErrorResponse(
        HttpStatusCode.Unauthorized, new HttpError());

    case HttpStatusCode.Forbidden:
      HttpResponseMessage errorResponse = 
        this.Request.CreateErrorResponse(HttpStatusCode.Forbidden, new HttpError());
      errorResponse.Headers.Add("CARD-ACTION-STATUS", 
        "Invalid sender or the action performer is not allowed.");
      return errorResponse;

    default:
      break;
  }

  string expenseId = jBody["id"].ToString();

  // Process and approve the expense report.

  HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK);
  response.Headers.Add("CARD-ACTION-STATUS", "The expense was approved.");

  return response;
}

Primeiro, o método verifica se existe um token de portador no cabeçalho Authorization. Depois, ele inicializa uma nova instância de Actionable­MessageTokenValidator e chama o método ValidateToken­Async. Esse método usa dois parâmetros. O primeiro é o próprio token de portador. O segundo é a URL base do serviço Web. No JWT decodificado, ele corresponde ao valor da declaração aud (público alvo). Essa declaração basicamente significa que o token foi emitido para o público alvo pretendido, que é o seu serviço Web, mas não para qualquer outro serviço Web. Neste caso, a API a ser chamada é http://api.contoso.com/expense/approve. O valor da declaração será a URL base, que é https://api.contoso.com.

O método retornará uma instância de ActionableMessage­TokenValidationResult. Verifique primeiro a propriedade ValidationSucceeded. Se a validação tiver sido concluída com êxito, o valor será true. Caso contrário, será false.

O resultado também inclui duas outras propriedades que serão úteis para terceiros. A primeira é Sender. Ela contém o valor da declaração sender no token, que corresponde ao endereço de email da conta que enviou a mensagem acionável. A segunda é ActionPerformer, que é o valor da declaração sub e corresponde ao endereço de email do usuário que executou a ação. Neste exemplo, apenas os endereços de email com <@contoso.com> podem aprovar ou rejeitar um relatório de despesas. Você pode substituir o código por um método de verificação mais complexo de sua autoria.

Cartão de atualização

Até agora, a única maneira de fornecer feedback para um usuário era pelo cabeçalho CARD-ACTION-STATUS. O valor do cabeçalho será exibido para o usuário em uma área reservada do cartão. Outra opção é retornar um cartão de atualização para o usuário. A ideia é substituir o cartão de ação atual por outro diferente. Você pode querer fazer isso por alguns motivos. Por exemplo, para evitar que outra pessoa aprove ou rejeite um relatório de despesas que você já aprovou. Então, você avisa o usuário que o relatório de despesas já foi aprovado. A Figura 10 mostra a marcação que será retornada.

Figura 10: Marcação retornada para o relatório de despesas com o cartão de atualização

{
  "@context": "http://schema.org/extensions",
  "@type": "MessageCard",
  "hideOriginalBody": "true",
  "title": "Expense report #98432019 was approved",
  "sections": [{
    "facts": [{
      "name": "ID",
      "value": "98432019"
    }, {
      "name": "Amount",
      "value": "83.27 USD"
    }, {
      "name": "Submitter",
      "value": "Kathrine Joseph"
    }, {
      "name": "Description",
      "value": "Dinner with client"
    }]
  }]
}

É necessário definir o valor do cabeçalho CARD-UPDATE-IN-BODY como true, para que os servidores da Microsoft saibam que a resposta tem um cartão de atualização. A Figura 11 mostra que o método Approve retorna um cartão de atualização.

Figura 11: O método Approve retorna um cartão de atualização

private HttpResponseMessage CreateRefreshCard(
  HttpRequestMessage request, string actionStatus, 
  string expenseID, string amount, string submitter, string description)
{
  string refreshCardFormatString = "{\"@context\": \"http://schema.org/extensions\",\"@type\": \"MessageCard\",\"hideOriginalBody\": \"true\",\"title\": \"Expense report #{0} was approved\",\"sections\": [{\"facts\": [{\"name\": \"ID\",\"value\": \"{0}\"},{\"name\": \"Amount\",\"value\": \"{1}\"},{\"name\": \"Submitter\",\"value\": \"{2}\"},{\"name\": \"Description\",\"value\": \"{3}\"}]}]}";
  string refreshCardMarkup = string.Format(
    refreshCardFormatString,
    expenseID,
    amount,
    submitter,
    description);

HttpResponseMessage response = request.CreateResponse(HttpStatusCode.OK);
Response.Headers.Add("CARD-ACTION-STATUS", actionStatus);
  response.Headers.Add("CARD-UPDATE-IN-BODY", "true");
  response.Content = new StringContent(refreshCardMarkup);

  return response;
}

[HttpPost]
[Route("approve")]
public async Task<HttpResponseMessage> Approve([FromBody]JObject jBody)
{
  HttpRequestMessage request = this.ActionContext.Request;
  HttpStatusCode result = await VerifyBearerToken(
    request, "https://api.contoso.com", 
    "expenseapproval@contoso.com");

  switch (result)
  {
    case HttpStatusCode.Unauthorized:
      return request.CreateErrorResponse(
        HttpStatusCode.Unauthorized, new HttpError());

    case HttpStatusCode.Forbidden:
      HttpResponseMessage errorResponse = 
        this.Request.CreateErrorResponse(
          HttpStatusCode.Forbidden, new HttpError());
      errorResponse.Headers.Add("CARD-ACTION-STATUS", 
        "Invalid sender or the action performer is not allowed.");
      return errorResponse;

    default:
      break;
  }

  string expenseId = jBody["id"].ToString();

  // Process and approve the expense report.

  return CreateRefreshCard(
    request,
    "The expense was approved.",
    "98432019",
    "83.27 USD",
    "Jonathan Kiev",
    "Dinner with client");
}

Conclusão

As Mensagens Acionáveis permitem que os usuários concluam tarefas com segurança diretamente no Outlook. Esse recurso já está disponível no Outlook para desktop e no Outlook Web Access, e será oferecido no Outlook para Mac e no Outlook Mobile em breve. É muito fácil implementar as Mensagens Acionáveis. Primeiro, você precisa adicionar a marcação necessária aos emails que está enviando. Depois, você precisa verificar o token de portador enviado pela Microsoft para o seu serviço Web. As Mensagens Acionáveis deixarão seus usuários mais felizes e produtivos. Infelizmente, não é possível falar sobre todos os aspectos das Mensagens Acionáveis neste artigo. Visite bit.ly/2rAD6AZ para obter informações mais completas e os links dos os códigos de exemplo.

Quero agradecer a Sohail Zafar, Edaena Salinas Jasso, Vasant Kumar Tiwari, Mark Encarnacion e Miaosen Wang, que me ajudaram a revisar a ortografia, a gramática e a fluência deste texto.


Woon Kiat Wong é engenheiro de software do Knowledge Technologies Group da Microsoft Research. Ele trabalha junto com a equipe do Outlook no desenvolvimento do recurso Mensagens Acionáveis. Entre em contato com ele pelo email wowong@microsoft.com.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Pretish Abraham, David Claux, Mark Encarnacion e Patrick Pantel


Discuta esse artigo no fórum do MSDN Magazine