Игры-викторины предоставляют увлекательный и познавательный опыт, где вы можете узнать новые факты и расширить свои знания по различным предметам. В настоящее время мобильные и веб-приложения викторин являются наиболее распространенными областями для такой деятельности. Как насчет того, чтобы сыграть в викторину в WhatsApp?
В этом учебном руководстве вы узнаете, как создать приложение викторины с помощью Twilio для WhatsApp, ASP.NET Core и
Чтобы получить эти вопросы, вы будете использовать The Trivia API, REST API, который позволяет разработчикам легко создавать приложения-викторины, предоставляя викторины с несколькими вариантами ответов. Чтобы узнать больше об API Trivia, посетите __ документацию API Trivia __.
Чтобы выполнить это руководство, вам понадобится:
Для начала, используя терминал оболочки в предпочтительном рабочем каталоге, выполните следующие команды, чтобы создать новый проект веб-API:
dotnet new webapi -n TwilioWhatsAppTriviaApp --no-openapi
Вторая команда в приведенном выше фрагменте создаст новый проект веб-API с указанным именем и без поддержки OpenAPI (Swagger). Если вы хотите использовать Swagger в проекте, просто опустите --no-openapi
в приведенной выше команде.
Перейдите в каталог проекта, выполнив эту команду:
cd TwilioWhatsAppTriviaApp
Установите
dotnet add package Twilio.AspNet.Core
Эта библиотека упрощает работу с веб-перехватчиками и API Twilio в приложении ASP.NET Core.
Откройте проект, используя предпочитаемую вами IDE. В папке «Контроллеры» удалите шаблонный файл контроллера шаблона WeatherForecastController.cs , а также удалите WeatherForcast.cs в каталоге проекта.
Создайте и запустите свой проект, чтобы убедиться, что все, что вы сделали до сих пор, работает хорошо, используя следующие команды:
dotnet build dotnet run
После успешного запуска проекта обратите внимание на все URL-адреса локального хоста, которые появляются в консоли отладки. Вы можете использовать любой из этих URL-адресов для настройки общедоступного локального веб-сервера с помощью ngrok.
Сеансы — это один из нескольких способов хранения пользовательских данных в приложении ASP.NET Core. Это важно, если вы хотите сохранять пользовательские данные между запросами, поскольку по умолчанию протокол HTTP не сохраняет состояние — это означает, что данные не сохраняются.
Добавьте поставщик сеансов в памяти, изменив Program.cs , как показано в следующем коде:
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()
регистрирует службу кэширования распределенной памяти. Эта служба предоставляет кэш в памяти, который можно использовать для хранения и извлечения данных по нескольким запросам или сеансам.
AddSession()
регистрирует службы сеанса, позволяя приложению поддерживать состояние сеанса. Параметр options
позволяет вам настроить различные параметры, связанные с сеансом. IdleTimeout
используется для установки продолжительности бездействия, после которой сеанс будет считаться бездействующим. В данном случае оно установлено на 40 секунд. Cookie.IsEssential
гарантирует, что состояние сеанса останется работоспособным даже в тех сценариях, где включено отклонение файлов cookie.
Поддержка сеансов включается путем добавления промежуточного программного обеспечения UseSession
в конвейер приложения, то есть ваше приложение получает доступ к объекту сеанса, который можно использовать для хранения и извлечения данных.
Создайте новую папку Models в каталоге вашего проекта. Добавьте два файла классов модели: TriviaApiResponse.cs и Question.cs со свойствами, как показано в следующих примерах кода:
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; } }
Модель TriviaApiResponse
включает свойства, которые представляют поля ответа Trivia API. Атрибут JsonProperty
гарантирует, что каждое свойство правильно заполнено соответствующими данными JSON.
Для упрощения обработки викторинных вопросов на помощь приходит класс Question
. Этот класс инкапсулирует необходимую информацию для викторинного вопроса, включая текст вопроса и список вариантов. Каждый параметр представлен кортежем, содержащим текст параметра и логическое значение, указывающее, является ли этот параметр правильным.
Создайте папку Services в каталоге вашего проекта и добавьте новый файл класса с именем TriviaService.cs . Измените его содержимое, как показано в следующем коде:
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; } }
Класс TriviaService
содержит два метода: GetTrivia
и ConvertTriviaToQuestions
. Метод GetTrivia
отправляет HTTP-запрос GET в Trivia API с параметром запроса limit=3
, который указывает, что должны быть возвращены только 3 вопроса. Без параметра limit API по умолчанию возвращает 10 вопросов.
Метод ConvertTriviaToQuestions
преобразует ответ API в организованный вид. Этот метод также случайным образом перемешивает все варианты вопросов, так что один вариант не будет ответом на все вопросы.
Чтобы зарегистрировать TriviaService
и HTTP-клиент в контейнере внедрения зависимостей (DI) вашего приложения, измените Program.cs , как показано в следующем коде:
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();
Добавьте пустой класс контроллера API в файл с именем TriviaController.cs в папку Controllers и измените его содержимое, как показано в следующем коде:
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; } }
Этот класс контроллера отвечает за обработку входящих сообщений, управление состоянием сеанса и генерацию ответов. Он наследуется от класса TwilioController
, предоставленного библиотекой Twilio.AspNet.Core, который предоставляет доступ к методу TwiML
. Вы можете использовать этот метод, чтобы ответитьTriviaController
использует методы HttpContext.Session
для взаимодействия с сеансом.
Допустимыми входными данными являются элементы массивов StartCommands
и OptionValues
, доступных только для чтения. Тело входящего сообщения сравнивается с этими элементами, чтобы убедиться, что пользователь отправил правильный ввод. В противном случае пользователю будет отправлено сообщение с предложением сделать правильный ввод на основе текущего состояния игры. Другие поля с префиксом «SessionKey» используются для определения частных константных строк для сеансовых ключей в программе.
Метод Index
— это основной метод действия, который обрабатывает входящие HTTP-запросы POST от WhatsApp по маршруту /Trivia . Он загружает данные сеанса с помощью HttpContext.Session.LoadAsync()
и извлекает данные о состоянии игры из сеанса с помощью методов HttpContext.Session.GetString()
и HttpContext.Session.GetInt32()
.
Использование подчеркиваний (_) и звездочек (*) в начале и конце определенных строк позволяет добиться форматирования текста курсивом и полужирным шрифтом соответственно в отображаемых сообщениях WhatsApp.
Каждый вспомогательный метод в TriviaController
выполняет определенную задачу, поддерживающую основную функциональность класса.
StartGame
инициализирует игру, извлекая викторины, преобразуя их в формат, подходящий для игры, и сохраняя их в сеансе.ProcessUserAnswer
обрабатывает ответ пользователя на вопрос и определяет, правильный он или нет.PresentQuestionWithOptions
отвечает за форматирование и представление вопроса вместе с его параметрами.AddNewQuestionsToSession
хранит список вопросов в сеансе. Он преобразует вопросы в формат JSON и сохраняет строку JSON в сеансе.RetrieveQuestionFromSession
извлекает вопрос из сеанса, используя индекс вопроса.EndTrivia
генерирует сообщение о завершении викторины. Этот метод также удаляет данные сеанса, связанные с игрой. В зависимости от конфигурации службы сеансов в Program.cs это происходит автоматически, когда сеанс бездействует в течение 40 секунд.
Чтобы протестировать приложение, вам необходимо настроить Twilio Sandbox для WhatsApp, сделать конечную точку вашего приложения общедоступной и добавить URL-адрес конечной точки в конфигурацию песочницы в качестве веб-перехватчика.
Перейти к
Следуйте инструкциям на странице, чтобы подключиться к песочнице, отправив сообщение WhatsApp со своего устройства на указанный номер Twilio, чтобы создать успешное соединение с песочницей WhatsApp. Аналогичным образом, другие люди, желающие протестировать ваше приложение со своими номерами, должны будут выполнить ту же процедуру.
Теперь откройте терминал оболочки и выполните следующую команду, чтобы запустить ngrok и предоставить доступ к локальному приложению ASP.NET Core, заменив <localhost-url>
полным URL-адресом вашего локального хоста, который вы изначально скопировали:
ngrok http <localhost-url>
ngrok сгенерирует общедоступный URL-адрес, который перенаправит запросы в ваше локальное приложение ASP.NET. Найдите URL-адрес пересылки с надписью «Пересылка» в окне терминала ngrok и скопируйте его.
Вернитесь на страницу Twilio Try WhatsApp, нажмите «Настройки песочницы » и измените URL-адрес конечной точки «Когда сообщение приходит на URL-адрес пересылки , сгенерированный ngrok, плюс маршрут /Trivia », и убедитесь, что для метода установлено значение POST. Затем нажмите «Сохранить», чтобы сохранить новую конфигурацию песочницы.
Запустите проект ASP.NET Core, используя следующую команду:
dotnet run
Теперь протестируйте свое приложение, отправив сообщение в первом разговоре с номером Twilio Sandbox.
Используя возможности платформы Twilio и WhatsApp, вы создали для пользователей захватывающую викторину. Вы также узнали, как сохранять и извлекать данные из сеансов.
Есть несколько способов улучшить этот проект. Вы можете улучшить этот проект, добавив таймер, обработав исключения, позволив пользователям выбирать сложность и применив выбранную сложность в качестве параметра запроса через URL-адрес Trivia API (например,