Os jogos de curiosidades proporcionam uma experiência envolvente e educacional onde você pode aprender novos fatos e expandir seu conhecimento em vários assuntos. Hoje em dia, os aplicativos móveis e da web do Trivia quiz são as áreas mais comuns para tal atividade. Que tal jogar um jogo de perguntas e respostas no WhatsApp?
Neste guia tutorial, você aprenderá como criar um aplicativo de perguntas e respostas usando Twilio para WhatsApp, ASP.NET Core e
Para obter essas perguntas, você usará a API Trivia, uma API REST, que facilita aos desenvolvedores a criação de aplicativos de teste, fornecendo perguntas triviais de múltipla escolha. Para saber mais sobre a API Trivia, visite a__documentação da API Trivia__ .
Para concluir este tutorial, você precisará de:
Para começar, usando seu terminal shell em um diretório de trabalho preferido, execute os seguintes comandos para criar um novo projeto de API da web:
dotnet new webapi -n TwilioWhatsAppTriviaApp --no-openapi
O segundo comando no trecho acima criará um novo projeto de API web com o nome especificado e sem suporte OpenAPI (Swagger). Se você quiser usar o Swagger no projeto, basta omitir --no-openapi
no comando acima.
Mude para o diretório do projeto executando este comando:
cd TwilioWhatsAppTriviaApp
Instale o
dotnet add package Twilio.AspNet.Core
Essa biblioteca simplifica o trabalho com webhooks e APIs da Twilio em um aplicativo ASP.NET Core.
Abra o projeto usando seu IDE preferido. Na pasta Controladores , remova o arquivo de controlador de modelo padrão, WeatherForecastController.cs e também remova o WeatherForcast.cs no diretório do projeto.
Crie e execute seu projeto para garantir que tudo o que você fez até agora funcione bem usando os seguintes comandos:
dotnet build dotnet run
Depois de executar o projeto com êxito, anote qualquer uma das URLs do host local que aparecem no console de depuração. Você pode usar qualquer um desses URLs para configurar um servidor web local acessível publicamente usando o ngrok.
As sessões são uma das diversas maneiras de armazenar os dados de um usuário em um aplicativo ASP.NET Core. Isso é essencial quando você deseja reter os dados do usuário entre solicitações porque, por padrão, o protocolo HTTP não tem estado – isso significa que os dados não são preservados.
Adicione o provedor de sessão na memória modificando Program.cs , conforme mostrado no código a seguir:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromSeconds(40); options.Cookie.IsEssential = true; }); var app = builder.Build(); app.UseSession(); app.MapControllers(); app.Run();
AddDistributedMemoryCache()
registra o serviço de cache de memória distribuída. Este serviço fornece um cache na memória que pode ser usado para armazenar e recuperar dados em várias solicitações ou sessões.
AddSession()
registra os serviços de sessão, permitindo que o aplicativo mantenha o estado da sessão. O parâmetro options
permite configurar diversas opções relacionadas à sessão. IdleTimeout
é usado para definir a duração da inatividade após a qual uma sessão será considerada inativa. Neste caso, está definido para 40 segundos. Cookie.IsEssential
garante que o estado da sessão permaneça funcional mesmo em cenários onde a rejeição de cookies está habilitada.
O suporte de sessão é habilitado adicionando o middleware UseSession
ao pipeline da aplicação, ou seja, sua aplicação ganha acesso a um objeto de sessão que pode ser usado para armazenar e recuperar dados.
Crie uma nova pasta, Modelos, no diretório do seu projeto. Adicione dois arquivos de classe de modelo, TriviaApiResponse.cs e Question.cs com propriedades conforme mostrado nos exemplos de código a seguir:
using Newtonsoft.Json; namespace TwilioWhatsAppTriviaApp.Models; public class TriviaApiResponse { [JsonProperty("category")] public string Category { get; set; } [JsonProperty("correctAnswer")] public string CorrectAnswer { get; set; } [JsonProperty("incorrectAnswers")] public List<string> IncorrectAnswers { get; set; } [JsonProperty("question")] public string Question { get; set; } [JsonProperty("type")] public string? Type { get; set; } [JsonProperty("difficulty")] public string Difficulty { get; set; } }
namespace TwilioWhatsAppTriviaApp.Models; public class Question { public string QuestionText { get; set; } public List<(string option, bool isCorrect)> Options { get; set; } }
O modelo TriviaApiResponse
inclui propriedades que representam os campos da resposta da API Trivia. O atributo JsonProperty
garante que cada propriedade seja preenchida corretamente com os dados JSON correspondentes.
Para uma maneira simplificada de lidar com perguntas triviais, a classe Question
vem em socorro. Esta classe encapsula as informações necessárias para uma pergunta trivial, incluindo o texto da pergunta e uma lista de opções. Cada opção é representada por uma tupla contendo o texto da opção e um valor booleano que indica se é a opção correta.
Crie uma pasta Services no diretório do seu projeto e adicione um novo arquivo de classe chamado TriviaService.cs . Modifique seu conteúdo, conforme mostrado no código a seguir:
using Newtonsoft.Json; using TwilioWhatsAppTriviaApp.Models; namespace TwilioWhatsAppTriviaApp.Services; public class TriviaService { private const string TheTriviaApiUrl = @"https://the-trivia-api.com/api/questions?limit=3"; private HttpClient httpClient; public TriviaService(HttpClient httpClient) { this.httpClient = httpClient; } public async Task<IEnumerable<TriviaApiResponse>> GetTrivia() { var response = await httpClient.GetAsync(TheTriviaApiUrl); var triviaJson = await response.Content.ReadAsStringAsync(); var trivia = JsonConvert.DeserializeObject<IEnumerable<TriviaApiResponse>>(triviaJson); return trivia; } public List<Question> ConvertTriviaToQuestions(IEnumerable<TriviaApiResponse> questions) { List<Question> newQuestions = new(); foreach (var question in questions) { var options = new List<(string option, bool isCorrect)>() { (question.CorrectAnswer, true), (question.IncorrectAnswers[0], false), (question.IncorrectAnswers[1], false), (question.IncorrectAnswers[2], false) }; // Shuffle the options randomly Random random = new(); options = options.OrderBy(_ => random.Next()).ToList(); newQuestions.Add(new Question { QuestionText = question.Question, Options = options }); } return newQuestions; } }
A classe TriviaService
contém dois métodos: GetTrivia
e ConvertTriviaToQuestions
. O método GetTrivia
envia a solicitação HTTP GET para a API Trivia com um parâmetro de consulta, limit=3
, que especifica que apenas 3 perguntas devem ser retornadas. Sem o parâmetro limit, a API retorna 10 perguntas por padrão.
O método ConvertTriviaToQuestions
converte a resposta da API de forma organizada. O método também embaralha todas as opções de perguntas aleatoriamente, de modo que uma única opção não seja a resposta para todas as perguntas.
Para registrar TriviaService
e o cliente HTTP no contêiner DI (Injeção de Dependência) do seu aplicativo, modifique Program.cs conforme mostrado no código a seguir:
using TwilioWhatsAppTriviaApp.Services; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromSeconds(40); options.Cookie.IsEssential = true; }); builder.Services.AddHttpClient(); builder.Services.AddScoped<TriviaService>(); var app = builder.Build(); app.UseSession(); app.MapControllers(); app.Run();
Adicione uma classe de controlador de API vazia em um arquivo chamado TriviaController.cs à pasta Controllers e modifique seu conteúdo conforme mostrado no código a seguir:
using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Twilio.AspNet.Core; using Twilio.TwiML; using Twilio.TwiML.Messaging; using TwilioWhatsAppTriviaApp.Models; using TwilioWhatsAppTriviaApp.Services; namespace WhatsappTrivia.Controllers; [Route("[controller]")] [ApiController] public class TriviaController : TwilioController { private const string SessionKeyIsGameOn = "IsGameOn"; private const string SessionKeyScore = "Score"; private const string SessionKeyCurrentQuestionIndex = "CurrentQuestionIndex"; private const string SessionKeyTotalQuestions = "TotalQuestions"; private const string SessionKeyQuestions = "Questions"; private static readonly string[] StartCommands = { "START", "S" }; private static readonly string[] OptionValues = { "A", "B", "C", "D" }; private readonly TriviaService triviaService; public TriviaController(TriviaService triviaService) { this.triviaService = triviaService; } [HttpPost] public async Task<IActionResult> Index() { var response = new MessagingResponse(); var form = await Request.ReadFormAsync(); var body = form["Body"].ToString().ToUpper().Trim(); await HttpContext.Session.LoadAsync(); var isGameOn = Convert.ToBoolean(HttpContext.Session.GetString(SessionKeyIsGameOn)); int currentQuestionIndex = HttpContext.Session.GetInt32(SessionKeyCurrentQuestionIndex) ?? 0; int totalQuestions = HttpContext.Session.GetInt32(SessionKeyTotalQuestions) ?? 0; if (StartCommands.Contains(body) && !isGameOn) { await StartGame(); HttpContext.Session.SetString(SessionKeyIsGameOn, "true"); response.Message(PresentQuestionWithOptions(currentQuestionIndex)); return TwiML(response); } if (OptionValues.Contains(body) && isGameOn) { var result = ProcessUserAnswer(body, currentQuestionIndex); response.Message(result); currentQuestionIndex++; if (currentQuestionIndex <= totalQuestions - 1) { HttpContext.Session.SetInt32(SessionKeyCurrentQuestionIndex, currentQuestionIndex); response.Append(new Message(PresentQuestionWithOptions(currentQuestionIndex))); } else { response.Append(new Message(EndTrivia())); } return TwiML(response); } response.Message(!isGameOn ? "*Hello! Send 'Start' or 'S' to play game*" : "*Invalid Input! Send a correct option 'A', 'B', 'C' or 'D'*"); return TwiML(response); } private async Task StartGame() { if (HttpContext.Session.GetString(SessionKeyQuestions) != null) { HttpContext.Session.Remove(SessionKeyQuestions); } var trivia = await this.triviaService.GetTrivia(); var questions = this.triviaService.ConvertTriviaToQuestions(trivia); AddNewQuestionsToSession(questions); HttpContext.Session.SetInt32(SessionKeyTotalQuestions, questions.Count); } private string ProcessUserAnswer(string userAnswer, int questionIndex) { bool optionIsCorrect = false; int score = HttpContext.Session.GetInt32(SessionKeyScore) ?? 0; var question = RetrieveQuestionFromSession(questionIndex); switch (userAnswer) { case "A": optionIsCorrect = question.Options[0].isCorrect; break; case "B": optionIsCorrect = question.Options[1].isCorrect; break; case "C": optionIsCorrect = question.Options[2].isCorrect; break; case "D": optionIsCorrect = question.Options[3].isCorrect; break; } if (optionIsCorrect) { score++; HttpContext.Session.SetInt32(SessionKeyScore, score); } return optionIsCorrect ? "_Correct ✅_" : $"_Incorrect ❌ Correct answer is {question.Options.Find(o => o.isCorrect).option.TrimEnd()}_"; } private string PresentQuestionWithOptions(int questionIndex) { var question = RetrieveQuestionFromSession(questionIndex); return $""" {questionIndex + 1}. {question.QuestionText} {OptionValues[0]}. {question.Options[0].option} {OptionValues[1]}. {question.Options[1].option} {OptionValues[2]}. {question.Options[2].option} {OptionValues[3]}. {question.Options[3].option} """; } private void AddNewQuestionsToSession(List<Question> questions) => HttpContext.Session.SetString(SessionKeyQuestions, JsonConvert.SerializeObject(questions)); private Question RetrieveQuestionFromSession(int questionIndex) { var questionsFromSession = HttpContext.Session.GetString(SessionKeyQuestions); return JsonConvert.DeserializeObject<List<Question>>(questionsFromSession)[questionIndex]; } private string EndTrivia() { var score = HttpContext.Session.GetInt32(SessionKeyScore) ?? 0; var totalQuestions = HttpContext.Session.GetInt32(SessionKeyTotalQuestions) ?? 0; var userResult = $""" Thanks for playing! 😊 You answered {score} out of {totalQuestions} questions correctly. To play again, send 'Start' or 'S' """; HttpContext.Session.Clear(); return userResult; } }
Esta classe controladora é responsável por lidar com mensagens recebidas, gerenciar o estado da sessão e gerar respostas. Ele herda da classe TwilioController
fornecida pela biblioteca Twilio.AspNet.Core, que dá acesso ao método TwiML
. Você pode usar este método para responder comTriviaController
usa métodos HttpContext.Session
para interagir com a sessão.
As entradas válidas são elementos nas matrizes somente leitura StartCommands
e OptionValues
. O corpo da mensagem recebida é comparado com esses elementos para garantir que o usuário enviou uma entrada correta; caso contrário, uma mensagem será enviada ao usuário solicitando que ele faça a entrada correta com base no estado atual do jogo. Outros campos com o prefixo “SessionKey” são usados para definir strings constantes privadas para chaves de sessão no programa.
O método Index
é o principal método de ação que lida com solicitações HTTP POST recebidas do WhatsApp por meio da rota /Trivia . Ele carrega os dados da sessão usando HttpContext.Session.LoadAsync()
e recupera dados relativos ao estado do jogo da sessão usando os métodos HttpContext.Session.GetString()
e HttpContext.Session.GetInt32()
.
O uso de sublinhados (_) e asteriscos (*) no início e no final de certas strings é para obter formatação de texto em itálico e negrito, respectivamente, em mensagens renderizadas do WhatsApp.
Cada método auxiliar no TriviaController
executa uma tarefa específica que oferece suporte à funcionalidade principal da classe.
StartGame
inicializa o jogo recuperando perguntas triviais, convertendo-as em um formato adequado para o jogo e armazenando-as na sessão.ProcessUserAnswer
processa a resposta do usuário a uma pergunta e determina se ela está correta ou não.PresentQuestionWithOptions
é responsável por formatar e apresentar uma pergunta junto com suas opções.AddNewQuestionsToSession
armazena uma lista de perguntas na sessão. Ele converte as perguntas para o formato JSON e salva a string JSON na sessão.RetrieveQuestionFromSession
recupera uma pergunta da sessão usando o índice de perguntas.EndTrivia
gera uma mensagem para encerrar o jogo de perguntas e respostas. Este método também remove dados de sessão relacionados ao jogo. Com base na configuração do serviço de sessão em Program.cs , isso acontece automaticamente quando a sessão fica inativa por 40 segundos.
Para testar o aplicativo, você precisa configurar o Twilio Sandbox para WhatsApp, tornar o endpoint do seu aplicativo acessível publicamente e adicionar o URL do endpoint na configuração do Sandbox como um webhook.
Vou ao
Siga as instruções na página para se conectar ao sandbox, enviando uma mensagem do WhatsApp do seu dispositivo para o número Twilio fornecido para criar uma conexão bem-sucedida com o sandbox do WhatsApp. Da mesma forma, outras pessoas que desejarem testar seu aplicativo com seus respectivos números deverão seguir o mesmo procedimento.
Agora, abra um terminal shell e execute o seguinte comando para iniciar o ngrok e expor seu aplicativo ASP.NET Core local, substituindo <localhost-url>
pela URL completa do seu host local que você copiou inicialmente:
ngrok http <localhost-url>
O ngrok gerará uma URL pública que encaminhará solicitações para seu aplicativo ASP.NET local. Procure o URL de encaminhamento denominado Encaminhamento na janela do terminal ngrok e copie-o.
Volte para a página Twilio Try WhatsApp, clique em Sandbox Settings e altere o URL do endpoint When a message comes in endpoint com o URL de encaminhamento gerado pelo ngrok mais a rota /Trivia e certifique-se de que o método esteja definido como POST. Em seguida, clique em Salvar para salvar a nova configuração do sandbox.
Execute seu projeto ASP.NET Core usando o seguinte comando:
dotnet run
Agora teste seu aplicativo enviando uma mensagem na conversa inicial com o número do Twilio Sandbox.
Ao aproveitar o poder da plataforma Twilio e do WhatsApp, você criou uma experiência imersiva de jogo de perguntas e respostas para os usuários aproveitarem. Você também aprendeu como salvar e recuperar dados de sessões.
Existem várias maneiras pelas quais este projeto pode ser melhorado. Você pode melhorar ainda mais este projeto adicionando um cronômetro, manipulando exceções, permitindo que os usuários escolham a dificuldade e aplicando a dificuldade escolhida como um parâmetro de consulta por meio do URL da API Trivia (por exemplo