LoginSignup
6
4

More than 3 years have passed since last update.

ASP.NET Core 3.0 Razor Pages 事始め(8) - 検索機能の追加

Last updated at Posted at 2019-11-13

記事目次

  1. ASP.NET Core 3.0 Razor Pages 事始め(1) - はじめてのRazor Pagesアプリケーション
  2. ASP.NET Core 3.0 Razor Pages 事始め(2) - スキャフォールディングとDBマイグレーション
  3. ASP.NET Core 3.0 Razor Pages 事始め(3) - マイグレーションのやり直しとURLルーティング
  4. ASP.NET Core 3.0 Razor Pages 事始め(4) - ページモデルとページハンドラ
  5. ASP.NET Core 3.0 Razor Pages 事始め(5) - Postページハンドラとタグヘルパー
  6. ASP.NET Core 3.0 Razor Pages 事始め(6) - データベースに初期値を設定する
  7. ASP.NET Core 3.0 Razor Pages 事始め(7) - Viewの変更とコンカレンシー例外処理
  8. ASP.NET Core 3.0 Razor Pages 事始め(8) - 検索機能の追加 <-- この記事
  9. ASP.NET Core 3.0 Razor Pages 事始め(9) - ページに新しいフィールドを追加する
  10. 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で絞り込めました。

スクリーンショット 2019-11-12 21.32.12.png

次に、デバッグを終了し、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 ="タイトル")]という属性を付加する必要がありますね。

では、テストしてみます。
うまく動いているようです。

スクリーンショット 2019-11-12 21.36.24.png

検索フィールドを空にして、[検索]ボタンを押せば、すべてのタイトルが表示されます。

ジャンルで検索

次にジャンルで検索する機能を追加します。

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="" としています。

ビルドが通ったら、ジャンルまたはムービーのタイトル、あるいはその両方で検索して、正しく動作するかをテストします。

正しく動作しているようです。

スクリーンショット 2019-11-12 21.42.26.png

スクリーンショット 2019-11-12 21.43.08.png

6
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4