セレクトボックスの表示
まずは、天気予報の地域を選択するセレクトボックスを表示する処理です。
CSVファイルの準備
気象庁の天気予報は、「予報区」と呼ばれる、全国、地方、府県の区域毎に発表されます(なお、府県といっても、都道府県単位というわけでなく、北海道などは複数に分割されています)。今回のアプリでは、府県予報区をセレクトボックスで選択できるようにしています。
府県区分の名称やそのコード番号などの情報は、気象庁の資料ページで公開されています。今回は、公開されている資料から、名称とコード番号をカンマ区切りにしたCSVファイルを自分で作成して利用します。1行は、コード番号、府県名という並びにし、日本語の文字コードは、UTF-8としました。
011000,宗谷地方 (略) 130000,東京都 (略) 474000,八重山地方
府県コードクラス
このCSVファイルのデータをC#で利用するために、データの入れ物となるモデルクラスを定義しておきましょう。コードと名称のプロパティがあるだけの単純なクラスです。このクラスは、クライアント、サーバーの両方のプロジェクトで共用しますので、~.Sharedの名前のプロジェクトに作成します。
// 府県予報区コードクラス public class FukenCode { public string Code { get; set; } // 府県予報区コード public string Name { get; set; } // 府県予報区名 }
府県コードを返すWebAPI
次に、ASP.NET Coreでのサーバー機能を使って、この府県コードをJSONデータで返すWebAPIを作成します。Blazor WebAssemblyのテンプレートで生成される、ASP.NET Coreサーバープロジェクトは、ASP.NET Core Web APIベースのテンプレートになっているため、このような処理も、少しのコードを追加するだけで済みます。
ASP.NET Core Web API
「ASP.NET Core Web API」は、ブラウザなどのクライアントに、RESTfulなHTTPサービス(Web API)を提供するためのフレームワークです。REST(REpresentational State Transfer)は、分散型システムにおけるソフトウェア連携の設計スタイルのことで、2000年に提唱されたものです。RESTには、HTTPベースのプロトコルであること、データの取得や更新などの操作は、すべてHTTPメソッド(GET、POST、PUT、DELETE)を利用すること、などの原則があります。このようなRESTの原則に基づいて設計されたものを、「RESTfulな○○」と呼びます。
また、ASP.NET Core Web APIフレームワークは、MVC(Model-View-Controller、モデル・ビュー・コントローラ)モデルをベースにしています。MVCとは、データやその処理を担う「Model」、UI出力処理の「View」、そして入力をモデルに伝える「Controller」という3つにアプリケーションを分離する設計モデルです。
Blazor用のテンプレートで生成されたサーバープロジェクトには、Controllersフォルダに、Controllerとなるクラスが生成されています。また、Pagesフォルダには、Viewにあたるファイルがあります。なお、Modelにあたるクラスは独立して生成されませんので、必要に応じて自分で追加することになります。
コントローラーの追加
それでは、先ほどのCSVファイルを使って、府県予報区コードのJSON形式データを返すAPIを作成してみます。定型的な処理は、ほとんどテンプレートに記述されていますので、実際に追加するコードは多くありません。
ソリューションエクスプローラーで、プロジェクト名.ServerにあるControllersフォルダを選択して、右クリックして表示されるメニューから、[追加]-[新しい項目]を選びます。そして次の画面では、「APIコントローラー-空」を選択し、ファイル名(ここでは、JmaController.cs)を入力して作成します。クラス名には、Controllerを付けるのが慣例です。
この操作で追加されるファイルは、ControllerBaseクラスを継承した、Web APIのコントローラークラス(JmaControllerクラス)となります。生成されたクラス内には何も定義されていないので、次のようなメソッド(ここではGetCodeメソッド)を追加します。
namespace BlazorTutorial2.Server.Controllers { [ApiController] [Route("api/[controller]")] // (1) public class JmaController : ControllerBase { [Route("code")] public IEnumerable<FukenCode> GetCode() { // CSVの読み込み(2) using var sr = new StreamReader(@"code.csv"); while (!sr.EndOfStream) { // CSVファイルの一行から府県コードオブジェクトを作成する(3) var ary = sr.ReadLine().Split(','); yield return new FukenCode { Code = ary[0], Name = ary[1] }; } } } }
なお、コントローラーとなるクラスには、次の3つが備わっている必要があります。
- [ApiController] 属性を付加する
- ControllerBaseクラスを継承する
- APIとなるメソッドに、ルーティングを示す属性を追加する
1と2は、テンプレートで生成されていますので、APIとなるメソッドと、3の属性を追加するだけです。ここでは、GetCodeメソッドを追加しています。
ルーティングを示す属性
ルーティング属性は、クライアントがリクエストするURLと、サーバーでの処理を関連づける属性です。ルーティング属性は、クラス定義の前にも、メソッドの前にも付加することができます。クラスの前に付加すると、すべてのメソッドに共通のルーティングが追加されることになります。
JmaControllerクラスの定義の前には、[Route("api/[controller]")]が追加されています(1)。[controller]は、トークンの置換と呼ばれる機能です。この[controller]は、コントローラー名(クラス名からControllerを除いたもの)に置換されます。ここでは、各APIのリクエストURLは、「ルート/api/jma/~」ということになります。
また、追加したGetCodeメソッドには、[Route("code")]と記述していますので、このメソッドのリクエストURLは、「ルート/api/jma/code」ということになります。
なお、直接"code"のような文字を書く代わりに、ここでもトークンの置換が使えます。トークンとして[action]と指定すれば、メソッド名がそのままルーティングに使われます。[action]を使って、同じ定義とするには、次のように記述します。
[Route([action])] public IEnumerable<FukenCode> Code()
GetCodeメソッドの処理
GetCodeメソッドでは、先のCSVファイルを読み込み、IEnumerator<FukenCode>型のオブジェクトを返しています。
CSVファイルのようなテキストファイルを読み込む方法は、いくつかありますが、今回は、StreamReaderクラスを利用しました(2)。StreamReaderクラスのコンストラクタで、CSVファイルを指定しています。このCSVファイルは、サーバープロジェクトの直下に配置しておきます。
StreamReaderクラスのReadLineメソッドで1行を文字列として読み取り、Splitメソッドで、配列に分割しています(3)。そして配列から、府県コードオブジェクトを生成しています。なお、yield returnを用いているため、別途コレクションオブジェクトなどに保持して、最後にそのオブジェクト返す処理は必要ありません。
ただし、yield returnは、try~catch構文の中では使用できないため、ファイルの読み込みエラーなどの例外処理を行いたい場合は、yield returnを使わずに、最後にreturnするコードのほうがシンプルになるでしょう。
戻り値のIEnumerator<FukenCode>型のオブジェクトからJSON形式への変換は、フレームワーク内で行われます。そのため、これだけのコードで、JSON形式のデータを返すAPIとなります。
ここまでのソースをビルドすると、「https://localhost:44336/api/jma/code」というURLで、次のようなJSONデータを参照することができます。なお、FukenCodeクラスの定義では、プロパティ名の1文字目は大文字でしたが、JSONデータに変換されるとプロパティはすべて小文字になります。
[ { "code": "011000", "name": "宗谷地方" }, ... ]
セレクトボックスの表示処理
それでは、このJSONデータを読み込み、セレクトボックスとして表示するページを作ってみましょう。といっても、テンプレートで自動生成された、FetchData.razorとほとんど同じ形になります。
@page "/tenki" @using BlazorTutorial2.Shared @inject HttpClient Http @if (codes == null) { <p><em>Loading...</em></p> } else { <select @bind="selectCode" class="form-control"> @foreach (var c in codes) { <option value="@c.Code">@c.Name</option> } </select> } @code { // 府県コードオブジェクトの配列 private FukenCode[] codes; // 選択された府県コード private string selectCode { get; set; } = "130000"; protected override async Task OnInitializedAsync() { // JSONデータを府県コードオブジェクトの配列に変換する codes = await Http.GetFromJsonAsync<FukenCode[]>("api/jma/code"); } }
最初に、@usingディレクティブにて、共有するクラスの名前空間をインポートしておきます。
JSONデータの読み込みは、最初にページが表示された際に非同期で呼び出される、OnInitializedAsyncメソッドのなかで行います。Http.GetFromJsonAsyncメソッドで、先ほど作成したWeb APIのURLを指定します。これで、フィールドの変数codesに、府県コードオブジェクトの配列が格納されます。
HTML部分では、セレクトボックスを作成する<select>タグ内に、変数codesをforeachループで参照し、<option>タグで要素を追加しています。
また、@bindディレクティブで、プロパティのselectCodeを指定しています。selectCodeは、双方向バインディングとなりますので、selectCodeの初期値と合致する、<option>要素が最初に表示されます。初期値は、東京のコード番号(130000)としていますので、セレクトボックスには、東京が表示されるはずです。
selectCodeプロパティには、セレクトボックスの値を変更するたびに、選択した<option>タグのvalue属性の値がセットされます。
最後に
今回は、天気予報アプリの前半部分を解説しました。次回は、天気予報が書かれたXMLファイルをダウンロードし、そのXMLをXSLスタイルシートを用いてページを表示する部分を解説することにします。