パート 6、ASP.NET Core Razor ページへの検索の追加

注

これは、この記事の最新バージョンではありません。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。

作成者: Rick Anderson

次のセクションでは、 ジャンル または名前で映画を検索する機能を追加 します。

次の強調表示されたコードを Pages/MoviePages/Index.cshtml.cs に追加します。

public class IndexModel : PageModel
{
    private readonly RazorPagesMovieContext _context;

    public IndexModel(RazorPagesMovieContext context)
    {
        _context = context;
    }

    public IList<Movie> Movie { get;set; }  = default!;

    [BindProperty(SupportsGet = true)]
    public string? SearchString { get; set; }

    public SelectList? Genres { get; set; }

    [BindProperty(SupportsGet = true)]
    public string? MovieGenre { get; set; }

前のコードでは:

  • SearchString: ユーザーが検索テキスト ボックスに入力したテキストが含まれる。 SearchString には、[BindProperty] 属性があります。 [BindProperty] により、プロパティと同じ名前のフォーム値とクエリ文字列がバインドされます。 HTTP GET 要求でのバインドには [BindProperty(SupportsGet = true)] が必要です。
  • Genres: ジャンル一覧が含まれる。 Genres により、ユーザーは一覧からジャンルを選択できます。 SelectList には using Microsoft.AspNetCore.Mvc.Rendering; が必要です。
  • MovieGenre: ユーザーが選択する特定のジャンルが含まれる。 たとえば、「Western」です。
  • Genres と MovieGenre は、このチュートリアルの後のほうで使用されます。

警告

セキュリティ上の理由から、ページ モデルのプロパティに対して GET 要求データのバインドをオプトインする必要があります。 プロパティにマップする前に、ユーザー入力を確認してください。 GET バインドをオプトインするのは、クエリ文字列やルート値に依存するシナリオに対処する場合に便利です。

GET 要求のプロパティをバインドするには、[BindProperty] 属性の SupportsGet プロパティを true に設定します。

[BindProperty(SupportsGet = true)]

詳細については、「ASP.NET Core コミュニティ スタンドアップ: GET でのバインドに関するディスカッション (YouTube)」を参照してください。

MoviePages/Index ページの OnGetAsync メソッドを次のコードで更新してください。

public async Task OnGetAsync()
{
    var movies = from m in _context.Movie
                 select m;
    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    Movie = await movies.ToListAsync();
}

OnGetAsync メソッドの最初の行により、ムービーを選択する LINQ クエリが作成されます。

var movies = from m in _context.Movie
             select m;

クエリは、この時点でのみ 定義 されます。 データベースに対して実行されません。

SearchString プロパティがnullまたは空でない場合は、検索文字列をフィルター処理するように movies クエリが変更されます。

if (!string.IsNullOrEmpty(SearchString))
{
    movies = movies.Where(s => s.Title.Contains(SearchString));
}

s => s.Title.Contains() コードはラムダ式です。 ラムダは、メソッド ベースの LINQ クエリで、Where メソッドや Contains メソッドなどの標準クエリ演算子メソッドの引数として使用されます。 LINQ クエリは、定義するときや、 Where、 Contains、 OrderByなどのメソッドを呼び出して変更する場合には実行されません。 クエリ実行は先送りされます。 その具体値が繰り返されるか、ToListAsync メソッドが呼び出されるまで、式の評価が延ばされます。 詳細については、「 クエリの実行」を参照してください。

注

Contains メソッドは、C# コードではなくデータベースで実行されます。 クエリの大文字と小文字の区別は、データベースや照合順序に依存します。 SQL Server では、Contains は大文字/小文字の区別がない SQL LIKE にマッピングされます。 既定の照合順序での SQLite は、クエリに応じて、大文字と小文字を区別する場合と区別しない場合が混在します。 大文字と小文字を区別しない SQLite クエリの作成については、次を参照してください。

MoviePages ページに移動し、URL にたとえば ?searchString=Ghost のようなクエリ文字列を追加します。 たとえば、https://localhost:7247/MoviePages?searchString=Ghost。 フィルターされたムービーが表示されます。

URL 内の検索文字列ゴーストと、返されたムービー リストを含むインデックス ビュー。

次のルート テンプレートを [インデックス] ページに追加すると、検索文字列を URL セグメントとして渡すことができます。 たとえば、https://localhost:7247/MoviePages/Ghost。

@page "{searchString?}"

先のルート制約では、クエリ文字列値の代わりに、ルート データ (URL セグメント) として題名を検索できます。 "{searchString?}" 内の ? は、これが任意のルート パラメーターであることを意味します。

Url にゴーストという単語が追加されたインデックス ビューと、ゴーストバスターとゴーストバスター 2 という 2 つの映画から返された映画の一覧。

ASP.NET Core ランタイムではモデル バインドを使用し、クエリ文字列 (?searchString=Ghost) またはルート データ (https://localhost:7247/MoviePages/Ghost) から SearchString プロパティの値が設定されます。 モデル バインドでは、大文字と小文字が区別されません。

ただし、ユーザーがムービーを検索するために URL を変更することは想定できません。 この手順では、ムービーをフィルター処理する UI を追加します。 ルート制約 "{searchString?}" を追加した場合、それを削除してください。

Pages/MoviePages/Index.cshtml ファイルを開き、次のコードで強調表示されているマークアップを追加してください。

@page
@model RazorPagesMovie.Pages.MoviePages.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    <thead>

HTML <form> タグでは、次のタグ ヘルパーが使用されます。

変更を保存し、フィルターをテストします。

タイトル フィルター テキスト ボックスにゴーストという単語が入力されたインデックス ビュー。

ジャンルで検索する

MoviePages/Index.cshtml.cs ページの OnGetAsync メソッドを次のコードで更新してください:

public async Task OnGetAsync()
{
    // <snippet_search_linqQuery>
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;
    // </snippet_search_linqQuery>

    var movies = from m in _context.Movie
                 select m;

    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    if (!string.IsNullOrEmpty(MovieGenre))
    {
        movies = movies.Where(x => x.Genre == MovieGenre);
    }

    // <snippet_search_selectList>
    Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
    // </snippet_search_selectList>
    Movie = await movies.ToListAsync();
}

次のコードは、データベースからすべてのジャンルを取得する LINQ クエリです。

IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

ジャンルの SelectList は、別個のジャンルを推定することで作成されます:

Genres = new SelectList(await genreQuery.Distinct().ToListAsync());

「Razor ページ」にジャンルでの検索を追加する

次のマークアップで強調表示されているように、Index.cshtml<form> 要素を更新します。

@page
@model RazorPagesMovie.Pages.MoviePages.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

ジャンルまたはムービーのタイトル、あるいはその両方で検索して、アプリをテストします。

ジャンル セレクターとタイトル テキスト ボックスの検索フィルターが含まれたインデックス ビュー。

次のステップ

次のセクションでは、ジャンルまたは名前による映画検索が追加されます。

次の強調表示されたコードを Pages/Movies/Index.cshtml.cs に追加します。

public class IndexModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    public IList<Movie> Movie { get;set; }  = default!;

    [BindProperty(SupportsGet = true)]
    public string? SearchString { get; set; }

    public SelectList? Genres { get; set; }

    [BindProperty(SupportsGet = true)]
    public string? MovieGenre { get; set; }

前のコードでは:

  • SearchString: ユーザーが検索テキスト ボックスに入力したテキストが含まれる。 SearchString には、[BindProperty] 属性があります。 [BindProperty] により、プロパティと同じ名前のフォーム値とクエリ文字列がバインドされます。 HTTP GET 要求でのバインドには [BindProperty(SupportsGet = true)] が必要です。
  • Genres: ジャンル一覧が含まれる。 Genres により、ユーザーは一覧からジャンルを選択できます。 SelectList には using Microsoft.AspNetCore.Mvc.Rendering; が必要です。
  • MovieGenre: ユーザーが選択する特定のジャンルが含まれる。 たとえば、「Western」です。
  • Genres と MovieGenre は、このチュートリアルの後のほうで使用されます。

警告

セキュリティ上の理由から、ページ モデルのプロパティに対して GET 要求データのバインドをオプトインする必要があります。 プロパティにマップする前に、ユーザー入力を確認してください。 GET バインドをオプトインするのは、クエリ文字列やルート値に依存するシナリオに対処する場合に便利です。

GET 要求のプロパティをバインドするには、[BindProperty] 属性の SupportsGet プロパティを true に設定します。

[BindProperty(SupportsGet = true)]

詳細については、「ASP.NET Core コミュニティ スタンドアップ: GET でのバインドに関するディスカッション (YouTube)」を参照してください。

Movies/Index ページの OnGetAsync メソッドを次のコードで更新してください。

public async Task OnGetAsync()
{
    var movies = from m in _context.Movie
                 select m;
    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    Movie = await movies.ToListAsync();
}

OnGetAsync メソッドの最初の行により、ムービーを選択する LINQ クエリが作成されます。

var movies = from m in _context.Movie
             select m;

このクエリは、この時点では "定義されている" だけであり、データベースに対して実行されているわけでは "ありません"。

SearchString プロパティが null でも空でもない場合、検索文字列で絞り込むようにムービークエリが変更されます。

if (!string.IsNullOrEmpty(SearchString))
{
    movies = movies.Where(s => s.Title.Contains(SearchString));
}

s => s.Title.Contains() コードはラムダ式です。 ラムダは、メソッド ベースの LINQ クエリで、Where メソッドや Contains メソッドなどの標準クエリ演算子メソッドの引数として使用されます。 LINQ クエリは、定義されたときにも、Where、Contains、OrderBy などのメソッドを呼び出して変更されたときにも実行されません。 クエリ実行は先送りされます。 その具体値が繰り返されるか、ToListAsync メソッドが呼び出されるまで、式の評価が延ばされます。 詳細については、「クエリ実行」を参照してください。

注

Contains メソッドは C# コードではなく、データベースで実行されます。 クエリの大文字と小文字の区別は、データベースや照合順序に依存します。 SQL Server では、Contains は大文字/小文字の区別がない SQL LIKE にマッピングされます。 既定の照合順序での SQLite は、クエリに応じて、大文字と小文字を区別する場合と区別しない場合が混在します。 大文字と小文字を区別しない SQLite クエリの作成については、次を参照してください。

ムービー ページに移動し、?searchString=Ghost のようなクエリ文字列を URL の末尾に追加します。 たとえば、https://localhost:5001/Movies?searchString=Ghost。 フィルターされたムービーが表示されます。

インデックス ビュー

次のルート テンプレートが Index ページに追加される場合、検索文字列を URL セグメントとして渡すことができます。 たとえば、https://localhost:5001/Movies/Ghost。

@page "{searchString?}"

先のルート制約では、クエリ文字列値の代わりに、ルート データ (URL セグメント) として題名を検索できます。 "{searchString?}" 内の ? は、これが任意のルート パラメーターであることを意味します。

ghost という単語が URL に追加された索引ビュー。Ghostbusters と Ghostbusters 2 という 2 本のムービーからなるムービーリストが返されています。

ASP.NET Core ランタイムではモデル バインドを使用し、クエリ文字列 (?searchString=Ghost) またはルート データ (https://localhost:5001/Movies/Ghost) から SearchString プロパティの値が設定されます。 モデル バインディングでは、大文字と小文字は区別されません。

ただし、URL を変更してムービーを検索することをユーザーに求めることはできません。 この手順では、ムービーを絞り込むための UI を追加します。 ルート制約 "{searchString?}" を追加した場合、それを削除してください。

Pages/Movies/Index.cshtml ファイルを開き、次のコードで強調表示されているマークアップを追加してください。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    <thead>

HTML <form> タグでは、次のタグ ヘルパーが使用されます。

変更を保存し、フィルターをテストします。

タイトル フィルター テキストボックスに ghost という単語が入力されたインデックス ビュー

ジャンルで検索する

Movies/Index.cshtml.cs ページの OnGetAsync メソッドを次のコードで更新してください:

public async Task OnGetAsync()
{
    // <snippet_search_linqQuery>
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;
    // </snippet_search_linqQuery>

    var movies = from m in _context.Movie
                 select m;

    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    if (!string.IsNullOrEmpty(MovieGenre))
    {
        movies = movies.Where(x => x.Genre == MovieGenre);
    }

    // <snippet_search_selectList>
    Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
    // </snippet_search_selectList>
    Movie = await movies.ToListAsync();
}

次のコードは、データベースからすべてのジャンルを取得する LINQ クエリです。

IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

ジャンルの SelectList は、別個のジャンルを推定することで作成されます:

Genres = new SelectList(await genreQuery.Distinct().ToListAsync());

「Razor ページ」にジャンルでの検索を追加する

次のマークアップで強調表示されているように、Index.cshtml<form> 要素を更新します。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

ジャンルまたはムービーのタイトル、あるいはその両方で検索して、アプリをテストします。

ジャンル セレクターとタイトル テキスト ボックスの検索フィルターを備えた完全なインデックス ビュー

次のステップ

次のセクションでは、ジャンルまたは名前による映画検索が追加されます。

次の強調表示されたコードを Pages/Movies/Index.cshtml.cs に追加します。

public class IndexModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    public IList<Movie> Movie { get;set; }  = default!;

    [BindProperty(SupportsGet = true)]
    public string? SearchString { get; set; }

    public SelectList? Genres { get; set; }

    [BindProperty(SupportsGet = true)]
    public string? MovieGenre { get; set; }

前のコードでは:

  • SearchString: ユーザーが検索テキスト ボックスに入力したテキストが含まれる。 SearchString には、[BindProperty] 属性があります。 [BindProperty] により、プロパティと同じ名前のフォーム値とクエリ文字列がバインドされます。 HTTP GET 要求でのバインドには [BindProperty(SupportsGet = true)] が必要です。
  • Genres: ジャンル一覧が含まれる。 Genres により、ユーザーは一覧からジャンルを選択できます。 SelectList には using Microsoft.AspNetCore.Mvc.Rendering; が必要です。
  • MovieGenre: ユーザーが選択する特定のジャンルが含まれる。 たとえば、「Western」です。
  • Genres と MovieGenre は、このチュートリアルの後のほうで使用されます。

警告

セキュリティ上の理由から、ページ モデルのプロパティに対して GET 要求データのバインドをオプトインする必要があります。 プロパティにマップする前に、ユーザー入力を確認してください。 GET バインドをオプトインするのは、クエリ文字列やルート値に依存するシナリオに対処する場合に便利です。

GET 要求のプロパティをバインドするには、[BindProperty] 属性の SupportsGet プロパティを true に設定します。

[BindProperty(SupportsGet = true)]

詳細については、「ASP.NET Core コミュニティ スタンドアップ: GET でのバインドに関するディスカッション (YouTube)」を参照してください。

索引ページの OnGetAsync メソッドを次のコードに変更します。

public async Task OnGetAsync()
{
    var movies = from m in _context.Movie
                 select m;
    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    Movie = await movies.ToListAsync();
}

OnGetAsync メソッドの最初の行により、ムービーを選択する LINQ クエリが作成されます。

// using System.Linq;
var movies = from m in _context.Movie
             select m;

このクエリは、この時点では "定義されている" だけであり、データベースに対して実行されているわけでは "ありません"。

SearchString プロパティが null でも空でもない場合、検索文字列で絞り込むようにムービークエリが変更されます。

if (!string.IsNullOrEmpty(SearchString))
{
    movies = movies.Where(s => s.Title.Contains(SearchString));
}

s => s.Title.Contains() コードはラムダ式です。 ラムダは、メソッド ベースの LINQ クエリで、Where メソッドや Contains メソッドなどの標準クエリ演算子メソッドの引数として使用されます。 LINQ クエリは、定義されたときにも、Where、Contains、OrderBy などのメソッドを呼び出して変更されたときにも実行されません。 クエリ実行は先送りされます。 その具体値が繰り返されるか、ToListAsync メソッドが呼び出されるまで、式の評価が延ばされます。 詳細については、「クエリ実行」を参照してください。

注

Contains メソッドは C# コードではなく、データベースで実行されます。 クエリの大文字と小文字の区別は、データベースや照合順序に依存します。 SQL Server では、Contains は大文字/小文字の区別がない SQL LIKE にマッピングされます。 既定の照合順序での SQLite は、クエリに応じて、大文字と小文字を区別する場合と区別しない場合が混在します。 大文字と小文字を区別しない SQLite クエリの作成については、次を参照してください。

ムービー ページに移動し、?searchString=Ghost のようなクエリ文字列を URL の末尾に追加します。 たとえば、https://localhost:5001/Movies?searchString=Ghost。 フィルターされたムービーが表示されます。

インデックス ビュー

次のルート テンプレートが Index ページに追加される場合、検索文字列を URL セグメントとして渡すことができます。 たとえば、https://localhost:5001/Movies/Ghost。

@page "{searchString?}"

先のルート制約では、クエリ文字列値の代わりに、ルート データ (URL セグメント) として題名を検索できます。 "{searchString?}" 内の ? は、これが任意のルート パラメーターであることを意味します。

ghost という単語が URL に追加された索引ビュー。Ghostbusters と Ghostbusters 2 という 2 本のムービーからなるムービーリストが返されています。

ASP.NET Core ランタイムではモデル バインドを使用し、クエリ文字列 (?searchString=Ghost) またはルート データ (https://localhost:5001/Movies/Ghost) から SearchString プロパティの値が設定されます。 モデル バインディングでは、大文字と小文字は区別されません。

ただし、URL を変更してムービーを検索することをユーザーに求めることはできません。 この手順では、ムービーを絞り込むための UI を追加します。 ルート制約 "{searchString?}" を追加した場合、それを削除してください。

Pages/Movies/Index.cshtml ファイルを開き、次のコードで強調表示されているマークアップを追加してください。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    @*Markup removed for brevity.*@

HTML <form> タグでは、次のタグ ヘルパーが使用されます。

変更を保存し、フィルターをテストします。

タイトル フィルター テキストボックスに ghost という単語が入力されたインデックス ビュー

ジャンルで検索する

Movies/Index.cshtml.cs ページの OnGetAsync メソッドを次のコードで更新してください:

public async Task OnGetAsync()
{
    // Use LINQ to get list of genres.
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;

    var movies = from m in _context.Movie
                 select m;

    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    if (!string.IsNullOrEmpty(MovieGenre))
    {
        movies = movies.Where(x => x.Genre == MovieGenre);
    }
    Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
    Movie = await movies.ToListAsync();
}

次のコードは、データベースからすべてのジャンルを取得する LINQ クエリです。

// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

ジャンルの SelectList は、別個のジャンルを推定することで作成されます。

Genres = new SelectList(await genreQuery.Distinct().ToListAsync());

「Razor ページ」にジャンルでの検索を追加する

次のマークアップで強調表示されているように、Index.cshtml<form> 要素を更新します。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

ジャンルまたはムービーのタイトル、あるいはその両方で検索して、アプリをテストします。

次のステップ

次のセクションでは、ジャンルまたは名前による映画検索が追加されます。

次の強調表示されたコードを Pages/Movies/Index.cshtml.cs に追加します。

public class IndexModel : PageModel
{
    private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

    public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
    {
        _context = context;
    }

    public IList<Movie> Movie { get;set; }  = default!;

    [BindProperty(SupportsGet = true)]
    public string? SearchString { get; set; }

    public SelectList? Genres { get; set; }

    [BindProperty(SupportsGet = true)]
    public string? MovieGenre { get; set; }

前のコードでは:

  • SearchString: ユーザーが検索テキスト ボックスに入力したテキストが含まれる。 SearchString には、[BindProperty] 属性があります。 [BindProperty] により、プロパティと同じ名前のフォーム値とクエリ文字列がバインドされます。 HTTP GET 要求でのバインドには [BindProperty(SupportsGet = true)] が必要です。
  • Genres: ジャンル一覧が含まれる。 Genres により、ユーザーは一覧からジャンルを選択できます。 SelectList には using Microsoft.AspNetCore.Mvc.Rendering; が必要です。
  • MovieGenre: ユーザーが選択する特定のジャンルが含まれる。 たとえば、「Western」です。
  • Genres と MovieGenre は、このチュートリアルの後のほうで使用されます。

警告

セキュリティ上の理由から、ページ モデルのプロパティに対して GET 要求データのバインドをオプトインする必要があります。 プロパティにマップする前に、ユーザー入力を確認してください。 GET バインドをオプトインするのは、クエリ文字列やルート値に依存するシナリオに対処する場合に便利です。

GET 要求のプロパティをバインドするには、[BindProperty] 属性の SupportsGet プロパティを true に設定します。

[BindProperty(SupportsGet = true)]

詳細については、「ASP.NET Core コミュニティ スタンドアップ: GET でのバインドに関するディスカッション (YouTube)」を参照してください。

索引ページの OnGetAsync メソッドを次のコードに変更します。

public async Task OnGetAsync()
{
    var movies = from m in _context.Movie
                 select m;
    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    Movie = await movies.ToListAsync();
}

OnGetAsync メソッドの最初の行により、ムービーを選択する LINQ クエリが作成されます。

// using System.Linq;
var movies = from m in _context.Movie
             select m;

このクエリは、この時点では "定義されている" だけであり、データベースに対して実行されているわけでは "ありません"。

SearchString プロパティが null でも空でもない場合、検索文字列で絞り込むようにムービークエリが変更されます。

if (!string.IsNullOrEmpty(SearchString))
{
    movies = movies.Where(s => s.Title.Contains(SearchString));
}

s => s.Title.Contains() コードはラムダ式です。 ラムダは、メソッド ベースの LINQ クエリで、Where メソッドや Contains メソッドなどの標準クエリ演算子メソッドの引数として使用されます。 LINQ クエリは、定義されたときにも、Where、Contains、OrderBy などのメソッドを呼び出して変更されたときにも実行されません。 クエリ実行は先送りされます。 その具体値が繰り返されるか、ToListAsync メソッドが呼び出されるまで、式の評価が延ばされます。 詳細については、「クエリ実行」を参照してください。

注

Contains メソッドは C# コードではなく、データベースで実行されます。 クエリの大文字と小文字の区別は、データベースや照合順序に依存します。 SQL Server では、Contains は大文字/小文字の区別がない SQL LIKE にマッピングされます。 既定の照合順序での SQLite は、クエリに応じて、大文字と小文字を区別する場合と区別しない場合が混在します。 大文字と小文字を区別しない SQLite クエリの作成については、次を参照してください。

ムービー ページに移動し、?searchString=Ghost のようなクエリ文字列を URL の末尾に追加します。 たとえば、https://localhost:5001/Movies?searchString=Ghost。 フィルターされたムービーが表示されます。

インデックス ビュー

次のルート テンプレートが Index ページに追加される場合、検索文字列を URL セグメントとして渡すことができます。 たとえば、https://localhost:5001/Movies/Ghost。

@page "{searchString?}"

先のルート制約では、クエリ文字列値の代わりに、ルート データ (URL セグメント) として題名を検索できます。 "{searchString?}" 内の ? は、これが任意のルート パラメーターであることを意味します。

ghost という単語が URL に追加された索引ビュー。Ghostbusters と Ghostbusters 2 という 2 本のムービーからなるムービーリストが返されています。

ASP.NET Core ランタイムではモデル バインドを使用し、クエリ文字列 (?searchString=Ghost) またはルート データ (https://localhost:5001/Movies/Ghost) から SearchString プロパティの値が設定されます。 モデル バインディングでは、大文字と小文字は区別されません。

ただし、URL を変更してムービーを検索することをユーザーに求めることはできません。 この手順では、ムービーを絞り込むための UI を追加します。 ルート制約 "{searchString?}" を追加した場合、それを削除してください。

Pages/Movies/Index.cshtml ファイルを開き、次のコードで強調表示されているマークアップを追加してください。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    @*Markup removed for brevity.*@

HTML <form> タグでは、次のタグ ヘルパーが使用されます。

変更を保存し、フィルターをテストします。

タイトル フィルター テキストボックスに ghost という単語が入力されたインデックス ビュー

ジャンルで検索する

索引ページの OnGetAsync メソッドを次のコードに変更します。

public async Task OnGetAsync()
{
    // Use LINQ to get list of genres.
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;

    var movies = from m in _context.Movie
                 select m;

    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    if (!string.IsNullOrEmpty(MovieGenre))
    {
        movies = movies.Where(x => x.Genre == MovieGenre);
    }
    Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
    Movie = await movies.ToListAsync();
}

次のコードは、データベースからすべてのジャンルを取得する LINQ クエリです。

// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

ジャンルの SelectList は、別個のジャンルを推定することで作成されます。

Genres = new SelectList(await genreQuery.Distinct().ToListAsync());

「Razor ページ」にジャンルでの検索を追加する

次のマークアップで強調表示されているように、Index.cshtml<form> 要素を更新します。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

ジャンルまたはムービーのタイトル、あるいはその両方で検索して、アプリをテストします。

次のステップ

次のセクションでは、ジャンルまたは名前による映画検索が追加されます。

次の強調表示されたコードを Pages/Movies/Index.cshtml.cs に追加します。

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace RazorPagesMovie.Pages.Movies
{
    public class IndexModel : PageModel
    {
        private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

        public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
        {
            _context = context;
        }

        public IList<Movie> Movie { get;set; } = default!;
        [BindProperty(SupportsGet = true)]
        public string ? SearchString { get; set; }
        public SelectList ? Genres { get; set; }
        [BindProperty(SupportsGet = true)]
        public string ? MovieGenre { get; set; }

前のコードでは:

  • SearchString: ユーザーが検索テキスト ボックスに入力したテキストが含まれる。 SearchString には、[BindProperty] 属性があります。 [BindProperty] により、プロパティと同じ名前のフォーム値とクエリ文字列がバインドされます。 HTTP GET 要求でのバインドには [BindProperty(SupportsGet = true)] が必要です。
  • Genres: ジャンル一覧が含まれる。 Genres により、ユーザーは一覧からジャンルを選択できます。 SelectList には using Microsoft.AspNetCore.Mvc.Rendering; が必要です。
  • MovieGenre: ユーザーが選択する特定のジャンルが含まれる。 たとえば、「Western」です。
  • Genres と MovieGenre は、このチュートリアルの後のほうで使用されます。

警告

セキュリティ上の理由から、ページ モデルのプロパティに対して GET 要求データのバインドをオプトインする必要があります。 プロパティにマップする前に、ユーザー入力を確認してください。 GET バインドをオプトインするのは、クエリ文字列やルート値に依存するシナリオに対処する場合に便利です。

GET 要求のプロパティをバインドするには、[BindProperty] 属性の SupportsGet プロパティを true に設定します。

[BindProperty(SupportsGet = true)]

詳細については、「ASP.NET Core コミュニティ スタンドアップ: GET でのバインドに関するディスカッション (YouTube)」を参照してください。

索引ページの OnGetAsync メソッドを次のコードに変更します。

public async Task OnGetAsync()
{
    var movies = from m in _context.Movie
                 select m;
    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    Movie = await movies.ToListAsync();
}

OnGetAsync メソッドの最初の行により、ムービーを選択する LINQ クエリが作成されます。

// using System.Linq;
var movies = from m in _context.Movie
             select m;

このクエリは、この時点では "定義されている" だけであり、データベースに対して実行されているわけでは "ありません"。

SearchString プロパティが null でも空でもない場合、検索文字列で絞り込むようにムービークエリが変更されます。

if (!string.IsNullOrEmpty(SearchString))
{
    movies = movies.Where(s => s.Title.Contains(SearchString));
}

s => s.Title.Contains() コードはラムダ式です。 ラムダは、メソッド ベースの LINQ クエリで、Where メソッドや Contains メソッドなどの標準クエリ演算子メソッドの引数として使用されます。 LINQ クエリは、定義されたときにも、Where、Contains、OrderBy などのメソッドを呼び出して変更されたときにも実行されません。 クエリ実行は先送りされます。 その具体値が繰り返されるか、ToListAsync メソッドが呼び出されるまで、式の評価が延ばされます。 詳細については、「クエリ実行」を参照してください。

注

Contains メソッドは C# コードではなく、データベースで実行されます。 クエリの大文字と小文字の区別は、データベースや照合順序に依存します。 SQL Server では、Contains は大文字/小文字の区別がない SQL LIKE にマッピングされます。 既定の照合順序での SQLite は、クエリに応じて、大文字と小文字を区別する場合と区別しない場合が混在します。 大文字と小文字を区別しない SQLite クエリの作成については、次を参照してください。

ムービー ページに移動し、?searchString=Ghost のようなクエリ文字列を URL の末尾に追加します。 たとえば、https://localhost:5001/Movies?searchString=Ghost。 フィルターされたムービーが表示されます。

インデックス ビュー

次のルート テンプレートが Index ページに追加される場合、検索文字列を URL セグメントとして渡すことができます。 たとえば、https://localhost:5001/Movies/Ghost。

@page "{searchString?}"

先のルート制約では、クエリ文字列値の代わりに、ルート データ (URL セグメント) として題名を検索できます。 "{searchString?}" 内の ? は、これが任意のルート パラメーターであることを意味します。

ghost という単語が URL に追加された索引ビュー。Ghostbusters と Ghostbusters 2 という 2 本のムービーからなるムービーリストが返されています。

ASP.NET Core ランタイムではモデル バインドを使用し、クエリ文字列 (?searchString=Ghost) またはルート データ (https://localhost:5001/Movies/Ghost) から SearchString プロパティの値が設定されます。 モデル バインディングでは、大文字と小文字は区別されません。

ただし、URL を変更してムービーを検索することをユーザーに求めることはできません。 この手順では、ムービーを絞り込むための UI を追加します。 ルート制約 "{searchString?}" を追加した場合、それを削除してください。

Pages/Movies/Index.cshtml ファイルを開き、次のコードで強調表示されているマークアップを追加してください。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    @*Markup removed for brevity.*@

HTML <form> タグでは、次のタグ ヘルパーが使用されます。

変更を保存し、フィルターをテストします。

タイトル フィルター テキストボックスに ghost という単語が入力されたインデックス ビュー

ジャンルで検索する

索引ページの OnGetAsync メソッドを次のコードに変更します。

public async Task OnGetAsync()
{
    // Use LINQ to get list of genres.
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;

    var movies = from m in _context.Movie
                 select m;

    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    if (!string.IsNullOrEmpty(MovieGenre))
    {
        movies = movies.Where(x => x.Genre == MovieGenre);
    }
    Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
    Movie = await movies.ToListAsync();
}

次のコードは、データベースからすべてのジャンルを取得する LINQ クエリです。

// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

ジャンルの SelectList は、別個のジャンルを推定することで作成されます。

Genres = new SelectList(await genreQuery.Distinct().ToListAsync());

「Razor ページ」にジャンルでの検索を追加する

次のマークアップで強調表示されているように、Index.cshtml<form> 要素を更新します。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

ジャンルまたはムービーのタイトル、あるいはその両方で検索して、アプリをテストします。

次のステップ

次のセクションでは、ジャンルまたは名前による映画検索が追加されます。

強調表示されている次の using ステートメントとプロパティを Pages/Movies/Index.cshtml.cs に追加します。

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace RazorPagesMovie.Pages.Movies
{

    public class IndexModel : PageModel
    {
        private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

        public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
        {
            _context = context;
        }

        public IList<Movie> Movie { get; set; }
        [BindProperty(SupportsGet = true)]
        public string SearchString { get; set; }
        public SelectList Genres { get; set; }
        [BindProperty(SupportsGet = true)]
        public string MovieGenre { get; set; }

前のコードでは:

  • SearchString: ユーザーが検索テキスト ボックスに入力したテキストが含まれる。 SearchString には、[BindProperty] 属性があります。 [BindProperty] により、プロパティと同じ名前のフォーム値とクエリ文字列がバインドされます。 HTTP GET 要求でのバインドには [BindProperty(SupportsGet = true)] が必要です。
  • Genres: ジャンル一覧が含まれる。 Genres により、ユーザーは一覧からジャンルを選択できます。 SelectList には using Microsoft.AspNetCore.Mvc.Rendering; が必要です。
  • MovieGenre: ユーザーが選択する特定のジャンルが含まれる。 たとえば、「Western」です。
  • Genres と MovieGenre は、このチュートリアルの後のほうで使用されます。

警告

セキュリティ上の理由から、ページ モデルのプロパティに対して GET 要求データのバインドをオプトインする必要があります。 プロパティにマップする前に、ユーザー入力を確認してください。 GET バインドをオプトインするのは、クエリ文字列やルート値に依存するシナリオに対処する場合に便利です。

GET 要求のプロパティをバインドするには、[BindProperty] 属性の SupportsGet プロパティを true に設定します。

[BindProperty(SupportsGet = true)]

詳細については、「ASP.NET Core コミュニティ スタンドアップ: GET でのバインドに関するディスカッション (YouTube)」を参照してください。

索引ページの OnGetAsync メソッドを次のコードに変更します。

public async Task OnGetAsync()
{
    var movies = from m in _context.Movie
                 select m;
    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    Movie = await movies.ToListAsync();
}

OnGetAsync メソッドの最初の行により、ムービーを選択する LINQ クエリが作成されます。

// using System.Linq;
var movies = from m in _context.Movie
             select m;

このクエリは、この時点では "定義されている" だけであり、データベースに対して実行されているわけでは "ありません"。

SearchString プロパティが null でも空でもない場合、検索文字列で絞り込むようにムービークエリが変更されます。

if (!string.IsNullOrEmpty(SearchString))
{
    movies = movies.Where(s => s.Title.Contains(SearchString));
}

s => s.Title.Contains() コードはラムダ式です。 ラムダは、メソッド ベースの LINQ クエリで、Where メソッドや Contains メソッドなどの標準クエリ演算子メソッドの引数として使用されます。 LINQ クエリは、定義されたときにも、Where、Contains、OrderBy などのメソッドを呼び出して変更されたときにも実行されません。 クエリ実行は先送りされます。 その具体値が繰り返されるか、ToListAsync メソッドが呼び出されるまで、式の評価が延ばされます。 詳細については、「クエリ実行」を参照してください。

注

Contains メソッドは C# コードではなく、データベースで実行されます。 クエリの大文字と小文字の区別は、データベースや照合順序に依存します。 SQL Server では、Contains は大文字/小文字の区別がない SQL LIKE にマッピングされます。 既定の照合順序での SQLite は、クエリに応じて、大文字と小文字を区別する場合と区別しない場合が混在します。 大文字と小文字を区別しない SQLite クエリの作成については、次を参照してください。

ムービー ページに移動し、?searchString=Ghost のようなクエリ文字列を URL の末尾に追加します。 たとえば、https://localhost:5001/Movies?searchString=Ghost。 フィルターされたムービーが表示されます。

インデックス ビュー

次のルート テンプレートが Index ページに追加される場合、検索文字列を URL セグメントとして渡すことができます。 たとえば、https://localhost:5001/Movies/Ghost。

@page "{searchString?}"

先のルート制約では、クエリ文字列値の代わりに、ルート データ (URL セグメント) として題名を検索できます。 "{searchString?}" 内の ? は、これが任意のルート パラメーターであることを意味します。

ghost という単語が URL に追加された索引ビュー。Ghostbusters と Ghostbusters 2 という 2 本のムービーからなるムービーリストが返されています。

ASP.NET Core ランタイムではモデル バインドを使用し、クエリ文字列 (?searchString=Ghost) またはルート データ (https://localhost:5001/Movies/Ghost) から SearchString プロパティの値が設定されます。 モデル バインディングでは、大文字と小文字は区別されません。

ただし、URL を変更してムービーを検索することをユーザーに求めることはできません。 この手順では、ムービーを絞り込むための UI を追加します。 ルート制約 "{searchString?}" を追加した場合、それを削除してください。

Pages/Movies/Index.cshtml ファイルを開き、次のコードで強調表示されているマークアップを追加してください。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <label>Title: <input type="text" asp-for="SearchString" /></label>
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">
    @*Markup removed for brevity.*@

HTML <form> タグでは、次のタグ ヘルパーが使用されます。

変更を保存し、フィルターをテストします。

タイトル フィルター テキストボックスに ghost という単語が入力されたインデックス ビュー

ジャンルで検索する

索引ページの OnGetAsync メソッドを次のコードに変更します。

public async Task OnGetAsync()
{
    // Use LINQ to get list of genres.
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;

    var movies = from m in _context.Movie
                 select m;

    if (!string.IsNullOrEmpty(SearchString))
    {
        movies = movies.Where(s => s.Title.Contains(SearchString));
    }

    if (!string.IsNullOrEmpty(MovieGenre))
    {
        movies = movies.Where(x => x.Genre == MovieGenre);
    }
    Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
    Movie = await movies.ToListAsync();
}

次のコードは、データベースからすべてのジャンルを取得する LINQ クエリです。

// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
                                orderby m.Genre
                                select m.Genre;

ジャンルの SelectList は、別個のジャンルを推定することで作成されます。

Genres = new SelectList(await genreQuery.Distinct().ToListAsync());

「Razor ページ」にジャンルでの検索を追加する

  1. 次のマークアップで強調表示されているように、Index.cshtml<form> 要素を更新します。

    @page
    @model RazorPagesMovie.Pages.Movies.IndexModel
    
    @{
        ViewData["Title"] = "Index";
    }
    
    <h1>Index</h1>
    
    <p>
        <a asp-page="Create">Create New</a>
    </p>
    
    <form>
        <p>
            <select asp-for="MovieGenre" asp-items="Model.Genres">
                <option value="">All</option>
            </select>
            <label>Title: <input type="text" asp-for="SearchString" /></label>
            <input type="submit" value="Filter" />
        </p>
    </form>
    
    <table class="table">
        @*Markup removed for brevity.*@
    
    
  2. ジャンルまたはムービーのタイトル、あるいはその両方で検索して、アプリをテストします。

次のステップ