トリビア ゲームは、新しい事実を学び、さまざまな主題にわたって知識を広げることができる、魅力的で教育的な体験を提供します。現在、トリビア クイズのモバイル アプリケーションや Web アプリケーションは、このようなアクティビティの最も一般的な分野です。 WhatsApp でトリビア ゲームをプレイしてみてはいかがですか?
このチュートリアル ガイドでは、WhatsApp、ASP.NET Core、および Twilio を使用してトリビア クイズ アプリケーションを作成する方法を学習します。
これらの質問を取得するには、REST API である Trivia API を使用します。これにより、開発者は多肢選択式のトリビア質問を提供することでクイズ アプリを簡単に構築できます。 Trivia API の詳細については、__ Trivia API ドキュメント__ を参照してください。
このチュートリアルを完了するには、次のものが必要です。
まず、優先作業ディレクトリでシェル端末を使用して、次のコマンドを実行して新しい Web API プロジェクトを作成します。
dotnet new webapi -n TwilioWhatsAppTriviaApp --no-openapi
上記のスニペットの 2 番目のコマンドは、指定された名前で OpenAPI (Swagger) サポートなしで新しい Web API プロジェクトを作成します。プロジェクトで Swagger を使用する場合は、上記のコマンドで--no-openapi
を省略してください。
次のコマンドを実行して、プロジェクト ディレクトリに移動します。
cd TwilioWhatsAppTriviaApp
をインストールします
dotnet add package Twilio.AspNet.Core
このライブラリにより、ASP.NET Core アプリケーションでの Twilio Webhook と API の操作が簡素化されます。
好みの IDE を使用してプロジェクトを開きます。 Controllersフォルダーで、ボイラープレート テンプレート コントローラー ファイルWeatherForecastController.csを削除し、プロジェクト ディレクトリ内のWeatherForcast.csも削除します。
次のコマンドを使用して、プロジェクトをビルドして実行し、これまでに行った作業がすべて正常に機能することを確認します。
dotnet build dotnet run
プロジェクトが正常に実行されたら、デバッグ コンソールに表示されるローカルホスト URL をメモします。これらの URL のいずれかを使用して、ngrok を使用してパブリックにアクセス可能なローカル Web サーバーをセットアップできます。
セッションは、ASP.NET Core アプリケーションにユーザーのデータを保存するいくつかの方法の 1 つです。これは、デフォルトでは 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を作成します。次のコード サンプルに示すようなプロパティを持つ 2 つのモデル クラス ファイル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
という 2 つのメソッドが含まれています。 GetTrivia
メソッドは、3 つの質問のみを返すことを指定するクエリ パラメーターlimit=3
指定して、HTTP GET リクエストを Trivia API に送信します。制限パラメータを指定しない場合、API はデフォルトで 10 個の質問を返します。
ConvertTriviaToQuestions
メソッドは、API からの応答を体系的な方法に変換します。また、このメソッドはすべての質問の選択肢をランダムにシャッフルするので、1 つの選択肢がすべての質問の答えになるわけではありません。
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();
TriviaController.csという名前のファイル内の空の API コントローラー クラスを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; } }
このコントローラー クラスは、受信メッセージの処理、セッション状態の管理、および応答の生成を担当します。これは、Twilio.AspNet.Core ライブラリによって提供されるTwilioController
クラスを継承しており、 TwiML
メソッドへのアクセスを提供します。このメソッドを使用して次のように応答できます。TriviaController
クラスは、 HttpContext.Session
メソッドを使用してセッションと対話します。
有効な入力は、 StartCommands
およびOptionValues
読み取り専用配列の要素です。受信メッセージの本文がこれらの要素と比較され、ユーザーが適切な入力を送信したかどうかが確認されます。送信されていない場合は、ゲームの現在の状態に基づいて正しい入力を行うよう求めるメッセージがユーザーに送信されます。 「SessionKey」プレフィックスが付いた他のフィールドは、プログラム内のセッション キーのプライベート定数文字列を定義するために使用されます。
Index
メソッドは、WhatsApp から/Triviaルート経由で受信する HTTP POST リクエストを処理するメインのアクション メソッドです。 HttpContext.Session.LoadAsync()
を使用してセッション データを読み込み、 HttpContext.Session.GetString()
メソッドとHttpContext.Session.GetInt32()
メソッドを使用してセッションからゲーム状態に関するデータを取得します。
特定の文字列の先頭と末尾にアンダースコア (_) とアスタリスク (*) を使用すると、表示される WhatsApp メッセージでそれぞれ斜体と太字のテキスト形式が実現されます。
TriviaController
の各ヘルパー メソッドは、クラスの主な機能をサポートする特定のタスクを実行します。
StartGame
メソッドは、トリビアの質問を取得し、ゲームに適した形式に変換し、セッションに保存することでゲームを初期化します。ProcessUserAnswer
メソッドは、質問に対するユーザーの回答を処理し、それが正しいかどうかを判断します。PresentQuestionWithOptions
メソッドは、質問をそのオプションとともにフォーマットして提示する役割を果たします。AddNewQuestionsToSession
メソッドは、セッション内の質問のリストを保存します。質問を JSON 形式に変換し、JSON 文字列をセッションに保存します。RetrieveQuestionFromSession
メソッドは、質問インデックスを使用してセッションから質問を取得します。EndTrivia
メソッドは、トリビア ゲームを終了するメッセージを生成します。このメソッドは、ゲームに関連するセッション データも削除します。 Program.csのセッション サービスの構成に基づいて、これはセッションが 40 秒間アイドル状態になると自動的に行われます。
アプリケーションをテストするには、WhatsApp 用に Twilio サンドボックスを設定し、アプリケーション エンドポイントをパブリックにアクセスできるようにし、サンドボックス構成にエンドポイント URL を Webhook として追加する必要があります。
に行きます
ページの指示に従ってサンドボックスに接続します。WhatsApp サンドボックスとの接続を成功させるために、デバイスから指定された Twilio 番号に WhatsApp メッセージを送信します。同様に、それぞれの番号を使用してアプリをテストしたい他の人も、同じ手順に従う必要があります。
次に、シェル ターミナルを開き、次のコマンドを実行して ngrok を起動し、 <localhost-url>
を最初にコピーしたローカルホストの完全な URL に置き換えて、ローカル ASP.NET Core アプリを公開します。
ngrok http <localhost-url>
ngrok は、リクエストをローカル ASP.NET アプリに転送するパブリック URL を生成します。 ngrok ターミナル ウィンドウでForwardingというラベルの付いた転送 URL を探してコピーします。
Twilio の Try WhatsApp ページに戻り、 [サンドボックス設定]をクリックし、エンドポイント URLにメッセージが届いたときを、 ngrok によって生成された転送URL に/Triviaルートを加えたものに変更し、メソッドが POST に設定されていることを確認します。次に、「保存」をクリックして新しいサンドボックス構成を保存します。
次のコマンドを使用して、ASP.NET Core プロジェクトを実行します。
dotnet run
ここで、最初の会話で Twilio サンドボックス番号とのメッセージを送信して、アプリケーションをテストします。
Twilio プラットフォームと WhatsApp の力を活用することで、ユーザーが楽しめる没入型のトリビア ゲーム エクスペリエンスを作成しました。また、セッションからデータを保存および取得する方法も学習しました。
このプロジェクトを改善する方法はいくつかあります。タイマーを追加し、例外を処理し、ユーザーが難易度を選択できるようにし、選択した難易度を Trivia API URL 経由でクエリ パラメーターとして適用することで、このプロジェクトをさらに改善できます (例: