ASP.NET Core 3.0 Razor Pages 事始め(7)の続きです。
今回は公式チュートリアルのASP.NET Core Razor ページへの検索の追加に沿って進めていこうと思います。
IndexModel へプロパティを追加
Indexページで検索機能を追加します。
まずは、IndexModelに、検索用のプロパティを追加します。
usingMicrosoft.AspNetCore.Mvc.Rendering;…publicclassIndexModel:PageModel{privatereadonlyRazorPagesMovie.Models.RazorPagesMovieContext_context;publicIndexModel(RazorPagesMovie.Models.RazorPagesMovieContextcontext){_context=context;}publicIList<Movie>Movies{get;set;}[BindProperty(SupportsGet=true)]publicstringSearchString{get;set;}// Requires using Microsoft.AspNetCore.Mvc.Rendering;publicSelectListGenres{get;set;}[BindProperty(SupportsGet=true)]publicstringMovieGenre{get;set;}publicasyncTaskOnGetAsync(){Movies=await_context.Movies.ToListAsync();}}
追加したのは、以下の3つのプロパティです。
SearchString
ユーザーが検索テキスト ボックスに入力した値がここに入ります。
[BindProperty] 属性で修飾されています。これで、HTMLの同じ名前のフォーム要素(あるいはクエリ文字列)がこのプロパティにバインドされます。 SupportsGet = true
は、GET 要求でのバインドで必要です。
Genres
ジャンル一覧を表しています。 選択一覧に表示される項目として利用されます。SelectList
クラスは Microsoft.AspNetCore.Mvc.Rendering
名前空間に定義されています。
MovieGenre
ユーザーが選択したジャンルが入ります。SearchString
プロパティと同様に、[BindProperty] 属性で修飾されています。
IndexModel の OnGetAsync メソッド変更
次に、IndexModel の OnGetAsync メソッドを変更します。変更後のOnGetAsyncメソッドを示します。
publicasyncTaskOnGetAsync(){varmovies=_context.MoviesasIQueryable<Movie>;if(!string.IsNullOrEmpty(SearchString)){movies=movies.Where(s=>s.Title.Contains(SearchString));}Movies=awaitmovies.ToListAsync();}
このOnGetAsync
メソッドが正しく動作するかを確認してみます。
F5キーでデバッグを開始します。
ブラウザが起動したら、Moviesページに移動します。
そして、?searchString=Ghost
のクエリ文字列を URLに追加して実行してみます。
https://localhost:5001/movies?searchString=Ghost
たしかに、Ghost
で絞り込めました。
次に、デバッグを終了し、index.chtmlを開きます。
先頭の行を以下のように書き換えます。
@page "{searchString?}"
これで、クエリ文字列の代わりに、URLのルートデータとして題名を指定できるようになります。これをルート制約と言うようです。
これも試してみます。
https://localhost:5001/movies/Ghost
先ほどと同じ結果になりました。
検索用UIの追加
しかし、URLをユーザに入力してもらうわけにはいきません。
そのため、index.cshtmlに検索のためのUIを追加します。
先ほどのルート制約を削除します。
それから、index.cshtmlを開き、以下のようの <form>
要素を <table>
要素の直前に追加します。
<p><aasp-page="Create">新規追加</a></p><form><p>
@Html.DisplayNameFor(model => model.Movies[0].Title):
<inputtype="text"asp-for="SearchString"/><buttontype="submit">検索</button></p></form><tableclass="table"><thead>
<form>
のデフォルトの methodの値は、"get"なので、検索ボタンを押せば、OnGetAsync
メソッドが呼びだされます。
チュートリアルのページでは、"Title"と文字列リテラルを使っていましたが、ここでは、@Html.DisplayNameForを使うようにしてみました。
HTML的には、<label>
を使ったほうが良いのかもしれませんね。でもその場合は、SearchString
プロパティに、[Display(Name ="タイトル")]
という属性を付加する必要がありますね。
では、テストしてみます。
うまく動いているようです。
検索フィールドを空にして、[検索]ボタンを押せば、すべてのタイトルが表示されます。
ジャンルで検索
次にジャンルで検索する機能を追加します。
index.cshtml.csの OnGetAsync
メソッドを次のように更新します。
なお、チュートリアルページのコードが僕の好みじゃないので書き換えています。
publicasyncTaskOnGetAsync(){// Moviesをフィルタリングvarmovies=_context.MoviesasIQueryable<Movie>;if(!string.IsNullOrEmpty(SearchString)){movies=movies.Where(s=>s.Title.Contains(SearchString));}if(!string.IsNullOrEmpty(MovieGenre)){movies=movies.Where(x=>x.Genre==MovieGenre);}Movies=awaitmovies.ToListAsync();// ジャンル一覧を生成vargenreList=await_context.Movies.OrderBy(m=>m.Genre).Select(m=>m.Genre).Distinct().ToListAsync();Genres=newSelectList(genreList);}
このほうが分かりやすいと個人的には思います。
次に、Index.cshtmlの<form>
要素を以下のように変更します。
<form><p><selectasp-for="MovieGenre"asp-items="Model.Genres"><optionvalue="">All</option></select>
@Html.DisplayNameFor(model => model.Movies[0].Title):
<inputtype="text"asp-for="SearchString"/><buttontype="submit">検索</button></p></form>
へー、こんな風に、<select>
要素内の<option>
の一部を htmlに書くこともできるんですね。
この場合は、asp-items
でバインドされた<option>
は、
<option value="">All</option>
の下に展開されるようです。以下展開された select要素です。
<selectid="MovieGenre"name="MovieGenre"><optionvalue="">All</option><option>Comedy</option><option>Romantic Comedy</option><option>Western</option></select>
ちなみに、All
を選んだときは、All
ではなく、空文字列がサーバーに送られるように、value=""
としています。
ビルドが通ったら、ジャンルまたはムービーのタイトル、あるいはその両方で検索して、正しく動作するかをテストします。
正しく動作しているようです。