記事目次
- ASP.NET Core 3.0 Razor Pages 事始め(1) - はじめてのRazor Pagesアプリケーション
- ASP.NET Core 3.0 Razor Pages 事始め(2) - スキャフォールディングとDBマイグレーション
- ASP.NET Core 3.0 Razor Pages 事始め(3) - マイグレーションのやり直しとURLルーティング
- ASP.NET Core 3.0 Razor Pages 事始め(4) - ページモデルとページハンドラ
- ASP.NET Core 3.0 Razor Pages 事始め(5) - Postページハンドラとタグヘルパー
- ASP.NET Core 3.0 Razor Pages 事始め(6) - データベースに初期値を設定する
- ASP.NET Core 3.0 Razor Pages 事始め(7) - Viewの変更とコンカレンシー例外処理
- ASP.NET Core 3.0 Razor Pages 事始め(8) - 検索機能の追加 <-- この記事
- ASP.NET Core 3.0 Razor Pages 事始め(9) - ページに新しいフィールドを追加する
- ASP.NET Core 3.0 Razor Pages 事始め(10) - 検証機能の追加
ASP.NET Core 3.0 Razor Pages 事始め(7)の続きです。
今回は公式チュートリアルのASP.NET Core Razor ページへの検索の追加に沿って進めていこうと思います。
IndexModel へプロパティを追加
Indexページで検索機能を追加します。
まずは、IndexModelに、検索用のプロパティを追加します。
using Microsoft.AspNetCore.Mvc.Rendering;
…
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Models.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Models.RazorPagesMovieContext context)
{
_context = context;
}
public IList<Movie> Movies { get;set; }
[BindProperty(SupportsGet = true)]
public string SearchString { get; set; }
// Requires using Microsoft.AspNetCore.Mvc.Rendering;
public SelectList Genres { get; set; }
[BindProperty(SupportsGet = true)]
public string MovieGenre { get; set; }
public async Task OnGetAsync()
{
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メソッドを示します。
public async Task OnGetAsync()
{
var movies = _context.Movies as IQueryable<Movie>;
if (!string.IsNullOrEmpty(SearchString)) {
movies = movies.Where(s => s.Title.Contains(SearchString));
}
Movies = await movies.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>
<a asp-page="Create">新規追加</a>
</p>
<form>
<p>
@Html.DisplayNameFor(model => model.Movies[0].Title):
<input type="text" asp-for="SearchString" />
<button type="submit">検索</button>
</p>
</form>
<table class="table">
<thead>
<form>
のデフォルトの methodの値は、"get"なので、検索ボタンを押せば、OnGetAsync
メソッドが呼びだされます。
チュートリアルのページでは、"Title"と文字列リテラルを使っていましたが、ここでは、@Html.DisplayNameForを使うようにしてみました。
HTML的には、<label>
を使ったほうが良いのかもしれませんね。でもその場合は、SearchString
プロパティに、[Display(Name ="タイトル")]
という属性を付加する必要がありますね。
では、テストしてみます。
うまく動いているようです。
検索フィールドを空にして、[検索]ボタンを押せば、すべてのタイトルが表示されます。
ジャンルで検索
次にジャンルで検索する機能を追加します。
index.cshtml.csの OnGetAsync
メソッドを次のように更新します。
なお、チュートリアルページのコードが僕の好みじゃないので書き換えています。
public async Task OnGetAsync() {
// Moviesをフィルタリング
var movies = _context.Movies as IQueryable<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 = await movies.ToListAsync();
// ジャンル一覧を生成
var genreList = await _context.Movies.OrderBy(m => m.Genre)
.Select(m => m.Genre)
.Distinct()
.ToListAsync();
Genres = new SelectList(genreList);
}
このほうが分かりやすいと個人的には思います。
次に、Index.cshtmlの<form>
要素を以下のように変更します。
<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
@Html.DisplayNameFor(model => model.Movies[0].Title):
<input type="text" asp-for="SearchString" />
<button type="submit">検索</button>
</p>
</form>
へー、こんな風に、<select>
要素内の<option>
の一部を htmlに書くこともできるんですね。
この場合は、asp-items
でバインドされた<option>
は、
<option value="">All</option>
の下に展開されるようです。以下展開された select要素です。
<select id="MovieGenre" name="MovieGenre">
<option value="">All</option>
<option>Comedy</option>
<option>Romantic Comedy</option>
<option>Western</option>
</select>
ちなみに、All
を選んだときは、All
ではなく、空文字列がサーバーに送られるように、value=""
としています。
ビルドが通ったら、ジャンルまたはムービーのタイトル、あるいはその両方で検索して、正しく動作するかをテストします。
正しく動作しているようです。