Los juegos de trivia brindan una experiencia atractiva y educativa en la que puedes aprender nuevos datos y ampliar tus conocimientos en diversos temas. Hoy en día, las aplicaciones web y móviles de Trivia Quiz son las áreas de acceso más comunes para este tipo de actividad. ¿Qué tal jugar un juego de preguntas en WhatsApp?
En esta guía tutorial, aprenderá cómo crear una aplicación de prueba de trivia usando Twilio para WhatsApp, ASP.NET Core y
Para obtener estas preguntas, utilizará The Trivia API, una API REST, que facilita a los desarrolladores la creación de aplicaciones de cuestionarios al proporcionar preguntas de trivia de opción múltiple. Para obtener más información sobre Trivia API, visite la __ documentación de Trivia API __.
Para completar este tutorial, necesitará:
Para comenzar, usando su terminal shell en un directorio de trabajo preferido, ejecute los siguientes comandos para crear un nuevo proyecto API web:
dotnet new webapi -n TwilioWhatsAppTriviaApp --no-openapi
El segundo comando en el fragmento anterior creará un nuevo proyecto de API web con el nombre especificado y sin soporte OpenAPI (Swagger). Si desea utilizar Swagger en el proyecto, simplemente omita --no-openapi
en el comando anterior.
Cambie al directorio del proyecto ejecutando este comando:
cd TwilioWhatsAppTriviaApp
Instala el
dotnet add package Twilio.AspNet.Core
Esta biblioteca simplifica el trabajo con webhooks y API de Twilio en una aplicación ASP.NET Core.
Abra el proyecto usando su IDE preferido. En la carpeta Controladores , elimine el archivo de controlador de plantilla repetitiva, WeatherForecastController.cs , y también elimine WeatherForcast.cs en el directorio del proyecto.
Construya y ejecute su proyecto para asegurarse de que todo lo que ha hecho hasta ahora funcione bien usando los siguientes comandos:
dotnet build dotnet run
Después de ejecutar exitosamente el proyecto, tome nota de cualquiera de las URL de localhost que aparecen en la consola de depuración. Puede utilizar cualquiera de estas URL para configurar un servidor web local de acceso público utilizando ngrok.
Las sesiones son una de las varias formas de almacenar los datos de un usuario en una aplicación ASP.NET Core. Esto es esencial cuando desea conservar los datos del usuario entre solicitudes porque, de forma predeterminada, el protocolo HTTP no tiene estado, lo que significa que los datos no se conservan.
Agregue el proveedor de sesión en memoria modificando Program.cs , como se muestra en el siguiente código:
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 el servicio de caché de memoria distribuida. Este servicio proporciona un caché en memoria que se puede utilizar para almacenar y recuperar datos en múltiples solicitudes o sesiones.
AddSession()
registra los servicios de la sesión, lo que permite que la aplicación mantenga el estado de la sesión. El parámetro options
le permite configurar varias opciones relacionadas con la sesión. IdleTimeout
se utiliza para establecer la duración de la inactividad después de la cual una sesión se considerará inactiva. En este caso, se establece en 40 segundos. Cookie.IsEssential
garantiza que el estado de la sesión siga siendo funcional incluso en escenarios donde el rechazo de cookies está habilitado.
El soporte de sesión se habilita agregando el middleware UseSession
a la canalización de la aplicación, es decir, su aplicación obtiene acceso a un objeto de sesión que se puede usar para almacenar y recuperar datos.
Cree una nueva carpeta, Modelos, en el directorio de su proyecto. Agregue dos archivos de clase de modelo, TriviaApiResponse.cs y Question.cs con propiedades como se muestra en los siguientes ejemplos de código:
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; } }
El modelo TriviaApiResponse
incluye propiedades que representan los campos de la respuesta de Trivia API. El atributo JsonProperty
garantiza que cada propiedad se complete correctamente con los datos JSON correspondientes.
Para una forma simplificada de manejar las preguntas de trivia, la clase Question
viene al rescate. Esta clase resume la información necesaria para una pregunta de trivia, incluido el texto de la pregunta y una lista de opciones. Cada opción está representada por una tupla que contiene el texto de la opción y un valor booleano que indica si es la opción correcta.
Cree una carpeta de Servicios en el directorio de su proyecto y agregue un nuevo archivo de clase llamado TriviaService.cs . Modifique su contenido, como se muestra en el siguiente código:
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; } }
La clase TriviaService
contiene dos métodos: GetTrivia
y ConvertTriviaToQuestions
. El método GetTrivia
envía la solicitud HTTP GET a la API de Trivia con un parámetro de consulta, limit=3
, que especifica que solo se deben devolver 3 preguntas. Sin el parámetro de límite, la API devuelve 10 preguntas de forma predeterminada.
El método ConvertTriviaToQuestions
convierte la respuesta de la API de forma organizada. El método también mezcla aleatoriamente todas las opciones de preguntas, de modo que una sola opción no será la respuesta a todas las preguntas.
Para registrar TriviaService
y el cliente HTTP en el contenedor de inyección de dependencia (DI) de su aplicación, modifique Program.cs como se muestra en el siguiente código:
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();
Agregue una clase de controlador API vacía en un archivo llamado TriviaController.cs a la carpeta Controladores y modifique su contenido como se muestra en el siguiente código:
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 clase de controlador es responsable de manejar los mensajes entrantes, administrar el estado de la sesión y generar respuestas. Hereda de la clase TwilioController
proporcionada por la biblioteca Twilio.AspNet.Core, que le brinda acceso al método TwiML
. Puede utilizar este método para responder conTriviaController
utiliza métodos HttpContext.Session
para interactuar con la sesión.
Las entradas válidas son elementos de las matrices de solo lectura StartCommands
y OptionValues
. El cuerpo del mensaje entrante se compara con estos elementos para garantizar que el usuario envió una entrada adecuada; de lo contrario, se enviará un mensaje al usuario solicitándole que realice la entrada correcta según el estado actual del juego. Otros campos con el prefijo "SessionKey" se utilizan para definir cadenas constantes privadas para las claves de sesión en el programa.
El método Index
es el método de acción principal que maneja las solicitudes HTTP POST entrantes de WhatsApp a través de la ruta /Trivia . Carga los datos de la sesión usando HttpContext.Session.LoadAsync()
y recupera datos sobre el estado del juego de la sesión usando los métodos HttpContext.Session.GetString()
y HttpContext.Session.GetInt32()
.
El uso de guiones bajos (_) y asteriscos (*) al principio y al final de ciertas cadenas es para lograr formato de texto en cursiva y negrita, respectivamente, en los mensajes de WhatsApp renderizados.
Cada método auxiliar en TriviaController
realiza una tarea específica que admite la funcionalidad principal de la clase.
StartGame
inicializa el juego recuperando preguntas de trivia, convirtiéndolas a un formato adecuado para el juego y almacenándolas en la sesión.ProcessUserAnswer
procesa la respuesta del usuario a una pregunta y determina si es correcta o no.PresentQuestionWithOptions
es responsable de formatear y presentar una pregunta junto con sus opciones.AddNewQuestionsToSession
almacena una lista de preguntas en la sesión. Convierte las preguntas al formato JSON y guarda la cadena JSON en la sesión.RetrieveQuestionFromSession
recupera una pregunta de la sesión utilizando el índice de preguntas.EndTrivia
genera un mensaje para finalizar el juego de trivia. Este método también elimina los datos de la sesión relacionados con el juego. Según la configuración del servicio de sesión en Program.cs , esto sucede automáticamente cuando la sesión está inactiva durante 40 segundos.
Para probar la aplicación, debe configurar Twilio Sandbox para WhatsApp, hacer que el punto final de su aplicación sea de acceso público y agregar la URL del punto final en la configuración de Sandbox como un webhook.
Ve a la
Siga las instrucciones en la página para conectarse al sandbox, enviando un mensaje de WhatsApp desde su dispositivo al número de Twilio proporcionado para crear una conexión exitosa con el sandbox de WhatsApp. De manera similar, otras personas que deseen probar su aplicación con sus respectivos números deberán seguir el mismo procedimiento.
Ahora, abra una terminal de shell y ejecute el siguiente comando para iniciar ngrok y exponer su aplicación ASP.NET Core local reemplazando <localhost-url>
con la URL completa de su localhost que copió inicialmente:
ngrok http <localhost-url>
ngrok generará una URL pública que reenviará solicitudes a su aplicación ASP.NET local. Busque la URL de reenvío denominada Reenvío en la ventana del terminal ngrok y cópiela.
Vuelva a la página Twilio Pruebe WhatsApp, haga clic en Configuración de Sandbox y cambie la URL del punto final Cuando llega un mensaje con la URL de reenvío generada por ngrok más la ruta /Trivia y asegúrese de que el método esté configurado en POST. Luego haga clic en Guardar para guardar la nueva configuración de la zona de pruebas.
Ejecute su proyecto ASP.NET Core usando el siguiente comando:
dotnet run
Ahora, pruebe su aplicación enviando un mensaje en la conversación inicial con el número de Twilio Sandbox.
Al aprovechar el poder de la plataforma Twilio y WhatsApp, ha creado una experiencia de juego de trivia inmersiva para que disfruten los usuarios. También aprendió a guardar y recuperar datos de sesiones.
Hay varias formas en que se puede mejorar este proyecto. Puede mejorar aún más este proyecto agregando un temporizador, manejando excepciones, permitiendo a los usuarios elegir la dificultad y aplicando la dificultad elegida como parámetro de consulta a través de la URL de la API de Trivia (p. ej.