ASP.NET Web ページの紹介 - HTML フォームの基本

作成者: Tom FitzMacken

このチュートリアルでは、入力フォームの作成方法の基本と、ASP.NET Web ページ (Razor) を使用する場合にユーザーの入力を処理する方法について説明します。 ここまででデータベースの作成が済んだので、次は、フォームのスキルを活用し、データベースから特定の映画を検索する機能をユーザーに提供することにします。 これは、「ASP.NET Web ページの概要 - データの表示」までのシリーズを完了した読者を対象とするチュートリアルです。

ここでは、次の内容について学習します。

  • 標準の HTML 要素を使用してフォームを作成する方法。
  • フォームでユーザーの入力内容を読み取る方法。
  • ユーザーから指定された検索語に基づいてデータを選択的に取得するための SQL クエリを作成する方法。
  • ユーザーの入力内容をページ内のフィールドに "記憶" させる方法。

取り上げる機能/テクノロジ:

  • Request オブジェクトです。
  • SQL Where 句。

作成するアプリケーション:

前のチュートリアルでは、データベースを作成し、そのデータベースにデータを追加し、WebGrid ヘルパーを使用してデータを表示しました。 このチュートリアルでは検索ボックスを追加して、特定のジャンルの映画や、入力された単語がタイトルに含まれている映画を検索できる機能を提供します (たとえば、ジャンルが "アクション" の映画、タイトルに "ハリー" や "冒険" が含まれている映画をすべて見つけられる機能)。

このチュートリアルを完了すると、以下のようなページができます。

Movies page with Genre and Title search

ページ内のリスト部分は、前のチュートリアルと同じグリッドです。 ただし、検索した映画のみがグリッドに表示される点が異なります。

HTML フォームについて

(HTML フォームの作成経験があり、GETPOST の違いを理解している方は、このセクションを読み飛ばしてください。)

フォームは、テキスト ボックス、ボタン、ラジオ ボタン、チェック ボックス、ドロップダウン リストなどのユーザー入力要素が含まれています。 ユーザーは、それらのコントロールに対して値の入力や選択を行い、ボタンをクリックしてフォームを送信します。

以下の例は、フォームの基本的な HTML 構文を示しています。

<form method="post">
  <input type="text" name="name" value="" />
  <br/>
  <input type="submit" name="submit" value="Submit" />
</form>

このマークアップがページ内で実行されると、以下の図のような単純なフォームが表示されます。

Basic HTML form as rendered in the browser

<form> 要素は、送信対象となる HTML 要素を囲みます (ページに要素を追加するとき、<form> 要素の外側に配置してしまうという間違いがよく起きます。その場合、追加した要素の情報は送信されません)。method 属性は、ユーザー入力をどのような方法で送信するかをブラウザーに指示します。 サーバー側でデータ更新が発生する操作の場合は post に設定し、サーバーからデータを取得するだけの場合は get に設定します。

ヒント

GET および POST と、HTTP 動詞の安全性

HTTP は、ブラウザーとサーバーの間の情報交換に使用されるプロトコルですが、そこで行われる基本的な操作はきわめて単純です。 ブラウザーがサーバーへの要求を行う際に使用する動詞は、ほんの数種類です。 Web 用のコードを記述する際には、それらの動詞の意味と、ブラウザーおよびサーバーでの使われ方を理解しておくとメリットがあります。 使われる機会が圧倒的に多い、最も一般的な動詞は以下の 2 つです。

  • GET: ブラウザーがサーバーから何かを取得するとき、この動詞を使用します。 たとえば、ユーザーから URL が入力された場合、ブラウザーは GET 操作を実行して目的のページを要求します。 ページ内にグラフィックが含まれる場合、ブラウザーは追加の GET 操作を実行して画像を取得します。 GET 操作でサーバーに情報を渡す必要がある場合、その情報は URL 内のクエリ文字列の一部として渡されます。
  • POST: サーバー上で追加や変更を行うためのデータを送信するとき、ブラウザーは POST 要求を送信します。 たとえば、POST 動詞は、データベースにレコードを作成する場合や既存のレコードを変更する場合に使用されます。 ユーザーがフォームに入力して送信ボタンをクリックすると、ほとんどの場合、ブラウザーでは POST 操作が実行されます。 POST 操作では、サーバーに渡すデータはページの本文に含められます。

これらの動詞の重要な違いとして、GET 操作は、サーバー上の何かを変化させることを想定して行うものではないという点が挙げられます。もう少し抽象的に言い換えるなら、GET 操作を実行してもサーバー上に状態の変化は起きません。 1 つのリソースに対して GET 操作を何回実行しても、その対象となったリソースは変化しません (しばしば、GET は "安全" な操作である、または専門的な用語で "べき等"な操作であると言われます)。一方、POST 要求はその反対で、実行するたびにサーバー上の何かを変化させるものです。

この違いを説明するために、2 つの例を挙げることにします。 Bing や Google などのエンジンを使用して検索を実行するとき、ユーザーは、テキスト ボックスを 1 つ含んだフォームに検索語を入力して検索ボタンをクリックします。 すると、ブラウザーによって GET 操作が実行され、ボックスに入力した値が URL に含めて送信されます。 この種のフォームで GET 操作を使用することは問題ありません。検索操作では情報を取得するだけであり、サーバー上のリソースを何も変更しないからです。

次に、オンラインで何かを注文するプロセスを考えてみましょう。 ユーザーは注文の詳細を入力して送信ボタンをクリックします。 この場合の操作は POST 要求になります。操作の結果として、新しい注文レコードが作成され、アカウント情報が更新され、場合によってその他多くの情報が変更されるなど、サーバー上に変化が発生するからです。 GET 操作の場合と違って、同じ POST 要求を繰り返すことはできません。もし繰り返すと、要求を再送信するたび、サーバー上に新しい注文が登録されることになります (この種の操作については、Web サイトが "送信ボタンを複数回クリックしないでください" と警告を表示することや、送信ボタンを無効にして意図しないフォーム再送信を防ぐことがよくあります)。

このチュートリアルでは、GET 操作と POST 操作の両方を使用して HTML フォームを操作しながら学習を進めます。 その中で、適切な動詞を使用すべきである理由をケースごとに説明します。

(HTTP 動詞の詳細については、W3C サイトの記事「Method Definitions」を参照してください。)

ほとんどのユーザー入力要素は、HTML の <input> 要素です。 この要素は、<input type="type" name="name">, (typeはユーザー入力コントロールの種類) のような形式を取ります。 以下の種類が一般的によく使用されます。

  • テキスト ボックス: <input type="text">
  • チェック ボックス: <input type="check">
  • ラジオ ボタン: <input type="radio">
  • ボタン: <input type="button">
  • 送信ボタン: <input type="submit">

また、複数行のテキスト ボックスを作成する <textarea> 要素や、ドロップダウン リストまたはスクロール可能リストを作成する <select> 要素も使用できます (HTML フォーム要素の詳細については、W3Schools サイトで、HTML フォームと入力に関するドキュメントを参照してください)。

name は非常に重要な属性です。後で説明するとおり、Web ページでは、受け取った値を取得する際にこの名前で要素を識別するからです。

そして本題は、ページ開発者がユーザーの入力に対して何を実行するかです。 これに関して、フォーム要素にあらかじめ関連付けられた組み込み動作はありません。 開発者がユーザーの入力値や選択値を取得し、それらに対して何らかの操作を実行する必要があります。 このチュートリアルでは、その方法について説明します。

ヒント

HTML5 と入力フォーム

HTML の仕様は変遷しつつあり、最新バージョンの HTML5 では、より直感的な情報入力方法をユーザーに提供できる機能がサポートされています。 たとえば、HTML5 の場合、ページ開発者はユーザーからの日付入力を求めていることをページに指示できます。 そうするとブラウザーには自動的にカレンダーが表示され、ユーザーは日付を手入力する必要がありません。 ただし、HTML5 は新しい仕様であるため、まだすべてのブラウザーでサポートされてはいません。

ASP.NET Web ページは、ユーザーの使用ブラウザーによってサポートされている範囲で HTML5 の入力に対応できます。 HTML5 で <input> 要素に設けられた新しい属性については、W3Schools サイトで、HTML <input> の type 属性に関するドキュメントを参照してください。

フォームの作成

WebMatrix の [ファイル] ワークスペースで、Movies.cshtmlページを開きます。

grid.GetHtml 呼び出しについて、</h1> 終了タグの後、<div> 開始タグの前に以下のマークアップを追加します。

<form method="get">
  <div>
    <label for="searchGenre">Genre to look for:</label>
    <input type="text" name="searchGenre" value="" />
    <input type="Submit" value="Search Genre" /><br/>
    (Leave blank to list all movies.)<br/>
    </div>
</form>

このマークアップでは、searchGenre というテキスト ボックスと送信ボタンを含んだフォームを作成します。 テキスト ボックスと送信ボタンを囲む <form> 要素の method 属性は get に設定しています (これらのテキスト ボックスと送信ボタンは同じ <form> 要素内に配置する必要があります。そうでないと、ボタンをクリックしても情報は送信されません)。ここで GET 動詞を使用するのは、このフォームは検索だけを実行するものであり、サーバー上に何も変化を起こさないからです。 (前のチュートリアルで使用した post メソッドは、変更内容をサーバーに送信するものです。この次のチュートリアルではそちらのメソッドを使用します)。

ページを実行します。 まだフォームの動作を定義していませんが、フォーム全体の表示がどのようになるかは確認できます。

Movies page with search box for Genre

テキスト ボックスに "Comedy" (コメディ) などの値を入力します。その後、[Search Genre (ジャンル検索)] をクリックします。

表示されたページの URL に注目してください。 この URL は以下の形式になっています。<form> 要素の method 属性を get に設定したため、入力した値がクエリ文字列の一部として URL に含まれています。

http://localhost:45661/Movies.cshtml?searchGenre=Comedy

フォーム内の値を読み取る

このページには、データベースからデータを取得して結果をグリッドに表示するコードが既に含まれています。 そこにコードを追加して、テキスト ボックスの値を読み取り、検索語を SQL クエリに含めて実行できるようにする必要があります。

このフォームのメソッドは get であるため、テキスト ボックスに入力された値を読み取るには、以下のようなコードを使用します。

var searchTerm = Request.QueryString["searchGenre"];

Request.QueryString オブジェクト (Request オブジェクトの QueryString プロパティ) には、GET 操作の中で送信された要素の値が含まれています。 Request.QueryString プロパティに格納される内容は、フォームから送信された値の "コレクション"(リスト) です。 その中から個別の値を取得する際には、目的の要素の名前を指定します。 テキスト ボックスの作成時、<input> 要素 (searchTerm) の name 属性を設定する必要があるのはこのためです (Request オブジェクトの詳細については、後に示すサイドバーを参照してください。)

テキスト ボックスの値を読み取ること自体は簡単です。 ただし、テキスト ボックスに何も入力しないまま [Submit (検索)] がクリックされた場合には、検索語がないのでクリックを無視してよいと考えられます。

その条件を実装するコードの一例を以下に示します (このコードを今すぐ追加する必要はありません。もう少し後で実装方法を説明します)。

if(!Request.QueryString["searchGenre"].IsEmpty() ) {
     // Do something here
}

このテスト コードでは以下のことを実行しています。

  • Request.QueryString["searchGenre"] の値、つまり、searchGenre という名前の <input> 要素に入力された値を取得します。
  • その値が空かどうかを IsEmpty メソッドで確認します。 これは、何か (フォーム要素など) に含まれている値が空かどうかを判断するとき標準的に使用されるメソッドです。 ただし、この場合の関心事は、値が "空ではない"かどうかです。
  • そこで、IsEmpty テストの前に ! 演算子を付けます (! 演算子は論理 NOT)。

要するに、この if 条件は "フォームの searchGenre 要素が空でない場合..."という意味です。

このブロックは、検索語を使用してクエリを作成するための下準備といえます。 これは、次のセクションで行います。

ヒント

Request オブジェクト

Request オブジェクトには、ページの要求または送信時にブラウザーからアプリケーションへと送信されるすべての情報が含まれます。 テキスト ボックスの値、アップロードするファイルなど、ユーザーから提供されるすべての情報がこのオブジェクトに格納されます。 また、あらゆる種類の追加情報も格納されます (たとえば Cookie や、URL クエリ文字列がある場合はその値、実行中のページのファイル パス、使用されているブラウザーの種類、ブラウザーに設定された使用言語の一覧、その他多数)。

Request オブジェクトは、値の "コレクション"(リスト) です。 コレクションの中から個別の値を取得するには、以下のように値の名前を指定します。

var someValue = Request["name"];

また、Request オブジェクトには数種類のサブセットが用意されています。 次に例を示します。

  • Request.Form では、要求の種類が POST の場合、送信された <form> 要素に含まれる一連の要素の値を取得できます。
  • Request.QueryString では、URL に含まれるクエリ文字列のみの値を取得できます (たとえば、URL が http://mysite/myapp/page?searchGenre=action&page=2 である場合、クエリ文字列は ?searchGenre=action&page=2 の部分です)。
  • Request.Cookies コレクションでは、ブラウザーによって送信された Cookie にアクセスできます。

送信されたフォーム内に確実に含まれている値を取得する際は、Request["name"] を使用できます。 また、より限定的な方法として、Request.Form["name"] (POST 要求の場合) または Request.QueryString["name"] (GET 要求の場合) を使用することもできます。 もちろん、nameは取得するアイテムの名前です。

取得するアイテムの名前は、そのコレクション内で一意である必要があります。 Request オブジェクトにサブセットの Request.FormRequest.QueryString などが用意されているのはそのためです。 たとえば、ページ内に userName というフォーム要素があり、かつuserName という Cookie も含まれている場合、 Request["userName"] では、フォーム値と Cookie 値のどちらを取得するためのアクセスなのか不明です。 一方、Request.Form["userName"] または Request.Cookie["userName"] を使用すると、何の値を取得するのかを明示することができます。

Request へのアクセスでは、サブセットである Request.FormRequest.QueryString などを使用して目的を具体的に示すことをお勧めします。 このチュートリアルで作成しているような単純なページの場合、おそらく実際はどちらも同じ結果になると思われます。 しかし、明示的に Request.Form または Request.QueryString を使用すると、1 つまたは複数のフォームや、Cookie、クエリ文字列値などを含んだ複雑なページを作成する場合に問題が発生することを回避できます。

検索語を使用してクエリを作成する

入力された検索語を取得する方法がわかったので、次は、それを基にしたクエリを作成します。 データベースからすべての映画アイテムを取得するには、ご存知のとおり、以下のような SQL クエリを使用します。

SELECT * FROM Movies

特定の映画のみを取得する場合は、クエリに Where 句を含めます。 この句では、クエリによって返される行の条件を指定できます。 次に例を示します。

SELECT * FROM Movies WHERE Genre = 'Action'

基本的な形式は WHERE column = value です。 演算子は = だけでなく、指示する条件に応じて、> (より大きい)、< (より小さい)、<> (等しくない)、<= (以下) など、さまざまな種類を使用できます。

ちなみに、SQL 文では大文字と小文字が区別されません。SELECTSelectselect はすべて同じように機能します。 ただし、慣習として、SQL 文を見やすくするため、SELECTWHERE のようなキーワードは多くの場合に大文字で表示されます。

検索語をパラメーターとして渡す

あらかじめ決めておいたジャンルを WHERE Genre = 'Action' のように検索するのは簡単ですが、ユーザーから入力された任意のジャンルを検索できるようにする必要があります。 これを行うには、検索する値のプレースホルダーを含んだ SQL クエリを作成します。 以下に示すコマンドはその一例です。

SELECT * FROM Movies WHERE Genre = @0

@ 文字に続いて 0 を記述した箇所がプレースホルダーです。 お察しのとおり、クエリには複数のプレースホルダーを含めることができ、その場合は @0@1@2... などのプレースホルダー名を使用します。

クエリを用意し、実際の値を渡すには、以下のようなコードを使用します。

selectCommand = "SELECT * FROM Movies WHERE Genre = @0";
selectedData = db.Query(selectCommand, Request.QueryString["searchGenre"]);

このコードは、グリッドにデータを表示するところで既に作成したコードと似ています。 唯一の違いは次のとおりです。

  • このクエリには 1 個のプレースホルダー (WHERE Genre = @0") が含まれています。
  • クエリを変数 (selectCommand) に格納しています。前の例では、クエリを db.Query メソッドに直接渡していました。
  • db.Query メソッドを呼び出すときは、クエリと一緒に、プレースホルダーに設定する値を渡します (クエリ内に複数のプレースホルダーがある場合は、それらに対応する個別の値をすべてメソッドに渡します)。

以上の要素をすべて取り入れると、コードは以下のようになります。

if(!Request.QueryString["searchGenre"].IsEmpty() ) { 
    searchTerm = Request.QueryString["searchGenre"];
    selectCommand = "SELECT * FROM Movies WHERE Genre = @0";
    selectedData = db.Query(selectCommand, searchTerm);
}

Note

重要! セキュリティの観点から、プレースホルダー (@0 など) を使用して SQL コマンドに値を渡すことはきわめて重要です。 SQL コマンドを構築する際は、必ずこの方法でプレースホルダーに変数データを設定し、他の方法は使用しないでください。

リテラル テキストとユーザーから取得した値を連結して SQL ステートメントを作成する方法は絶対に採用しないでください。 ユーザーからの入力を連結することで SQL ステートメントを構築すると、"SQL インジェクション攻撃"(悪意あるユーザーが特殊な値を送信してデータベースをハッキングする手法) に対し、サイトに脆弱性が生まれやすくなります (詳細については、MSDN Web サイトで、SQL インジェクションに関する記事を参照してください)。

検索コードで映画ページを更新する

Movies.cshtml ファイルのコードを更新する準備ができました。 まず、ページ最上部にあるコード ブロックのコードを、以下のコードに置き換えます。

var db = Database.Open("WebPagesMovies");
var selectCommand = "SELECT * FROM Movies";
var searchTerm = "";

以前との違いは、クエリを selectCommand 変数にいったん格納し、それを後で db.Query に渡すようにした点です。 SQL ステートメントを変数に格納すると、ステートメントを取り替えられるようになります。実際、この後で検索を実行する際にそれを行います。

また、削除した以下の 2 行は後で元に戻します。

var selectedData = db.Query("SELECT * FROM Movies");
var grid = new WebGrid(source: selectedData, rowsPerPage: 3);

まだ現時点ではクエリを実行しません (db.Query を呼び出しません)。また、WebGrid ヘルパーの初期化も行いません。 それらは、どの SQL ステートメントを実行するかを確定した後に行います。

この書き換えたブロックの後に、検索を処理するための新しいロジックを追加します。 完成したコードは下のようになります。 作成中のページ内のコードを、以下に示す例に合わせて更新してください。

@{
    var db = Database.Open("WebPagesMovies") ;
    var selectCommand = "SELECT * FROM Movies";
    var searchTerm = "";

    if(!Request.QueryString["searchGenre"].IsEmpty() ) {
        selectCommand = "SELECT * FROM Movies WHERE Genre = @0";
        searchTerm = Request.QueryString["searchGenre"];
    }

    var selectedData = db.Query(selectCommand, searchTerm);
    var grid = new WebGrid(source: selectedData, defaultSort: "Genre", rowsPerPage:3);
}

このページの動作は以下のとおりです。 まず、ページが実行されるたび、コードがデータベースを開き、Movies テーブルからすべてのレコードを取得する SQL ステートメントを selectCommand 変数に格納します。 次に、searchTerm 変数を初期化します。

その後、今回受け取った要求に searchGenre 要素の値が含まれている場合は、selectCommand の内容を別のクエリ (ジャンルを絞り込む Where 句を含んだクエリ) に変更します。 また、検索ボックスに入力された内容を searchTerm に格納します (空である可能性もあります)。

selectCommand にどちらの SQL ステートメントが含まれていても、このコードは db.Query を呼び出してクエリを実行し、searchTerm に含まれている値を渡します。 searchTerm の内容が空である場合、selectCommand にはパラメーターから値を受け取るプレースホルダーがないため、空であることは問題になりません。

最後は、以前の例と同様に、クエリ結果に基づいて WebGrid ヘルパーを初期化します。

以上のように、SQL ステートメントと検索語を変数に格納したことで、コードに柔軟性が加わっています。 このチュートリアルの後半では、ここで完成させた基本的なフレームワークを基に、さまざまな種類の検索ロジックを追加していきます。

ジャンル検索機能をテストする

WebMatrix で、Movies.cshtml ページを実行します。 ジャンルのテキスト ボックスを含んだページが表示されます。

いずれかのテスト レコードに入力したのと同じジャンルを入力し、[Search (検索)] をクリックします。 すると、そのジャンルに一致する映画だけがリストに表示されます。

Movies page listing after searching for genre 'Comedies'

別のジャンルを入力し、もう一度検索してみてください。 入力するジャンル名をすべて小文字にしたり、すべて大文字にしたりと変化させ、検索語の大文字と小文字が区別されていないことを確認してください。

ユーザーの入力内容を "記憶" する

ジャンルを入力して [Search Genre (ジャンル検索)] をクリックした後、そのジャンルのリストが表示されましたが、 検索テキスト ボックスは空になり、入力内容は記憶されなかったことに気付いたでしょうか。

このような動作になった理由を認識することは重要です。 ユーザーによって送信操作が実行されると、ブラウザーは Web サーバーに要求を送信します。 ASP.NET はその要求を受け取ると、ページの新しいインスタンスを作成し、その中のコードを実行して、ブラウザー上にページの新しいレンダリングを表示します。 つまり、新しいページは、同じページの以前のバージョンでどのような操作が行われたのかを知りません。 わかっているのは、何らかのフォーム データを含んだ要求を受け取ったことだけです。

初期ページの要求か、検索語を送信するための要求かに関係なく、ページが要求されるたび、Web サーバーは新しいページを表示します。 そのとき、直前に行われた要求についての記憶はもうありません。 Web サーバー、ASP.NET、ブラウザーのいずれも同様です。 異なるページ インスタンス間を結びつける唯一のつながりは、それらの間でやり取りされるデータだけです。 たとえば、ユーザーによって送信操作が行われると、新しいページ インスタンスは、以前のインスタンスによって送られたフォーム データを受け取ることができます (別の方法として、Cookie を使用してページ間でデータの受け渡しをすることも可能です)。

こうした状況が発生するのは、技術的にいえば、Web ページは "ステートレス"だからです。 ページの以前の状態に関する情報は、Web サーバーにも、ページ自体にも、ページ内の要素にも保持されません。 Web の設計がそのようになっているのは、もし個々の要求の状態を保持しようとすれば、Web サーバーのリソースがすぐに尽きてしまうからです。Web サーバーは毎秒何千、何万、何十万という数の要求を処理することが求められるため、これは切実な問題です。

テキスト ボックスが空になるのは、以上のような理由によります。 ユーザーからページが送信されると、ASP.NET はそのページの新しいインスタンスを作成し、コードとマークアップを実行します。 その際、テキスト ボックスに値を入力することを ASP.NET に指示するコードはありませんでした。 したがって ASP.NET は何もせず、テキスト ボックスを値なしでレンダリングしていました。

この問題には、実は簡単な回避方法があります。 テキスト ボックスに入力されたジャンルは、コード内で Request.QueryString["searchGenre"] を参照するだけで取得できるからです。

そこで、テキスト ボックスのマークアップを以下の例のように変更し、value 属性に searchTerm から取得した値を設定するようにします。

<input type="text" name="searchGenre" value="@Request.QueryString["searchGenre"]" />

このページの場合、value 属性に searchTerm 変数の値を設定することもできます。この変数には入力されたジャンルが格納されているからです。 しかし、この目的を達するには、このように Request オブジェクトから値を取得して value 属性を設定するのが標準的な方法です (もちろん、それが常に望ましいとは限りません。フィールドを必ず空にしてページをレンダリングしないと問題が生じる状況などもあり得ます。すべてはアプリで何を行うか次第です)。

Note

パスワード用のテキスト ボックスに入力された値を "記憶" してはいけません。 他人がそのコードを利用してパスワードを入力できるようになり、セキュリティ ホールが発生するおそれがあります。

もう一度ページを実行し、ジャンルを入力して [Search Genre (ジャンル検索)] をクリックします。 検索結果が表示されるだけでなく、テキスト ボックスにも前回の入力内容が表示されるようになりました。

Page showing that the text box has 'remembered' the previous entry

タイトルに含まれる任意の言葉を検索する

ここまでで任意のジャンルを検索できるようになりましたが、さらに、タイトルも検索できると便利です。 検索時に厳密なタイトルを入力することは難しいので、タイトル内のどこかに含まれる言葉を指定して検索できるようにします。 SQL でこれを行うには、以下のような構文と LIKE 演算子を使用します。

SELECT * FROM Movies WHERE Title LIKE '%adventure%'

このコマンドは、タイトルに "adventure" が含まれているすべての映画を検索します。 LIKE 演算子を使用するときは、検索語の中にワイルドカード文字 "%" を含めます。 検索語として LIKE 'adventure%' を指定すると、"先頭部分が 'adventure' で始まるもの" という意味になります (技術的に文字通り読めば、"文字列 'adventure' の後に任意の文字列が続くもの")。同様に、検索語 LIKE '%adventure' は "任意の文字列の後に 'adventure' が続くもの"、つまり "末尾が 'adventure' で終わるもの" を意味します。

したがって、検索語 LIKE '%adventure%' は "タイトル内のどこかに 'adventure' が含まれるもの" という意味になります (技術的には、"任意の文字列の後に 'adventure' が続き、またその後に任意の文字列が続くもの")。

<form> 要素の囲みの中に、ジャンル検索部分の終了 </div> タグに続けて (終了 </form> 要素の直前に) 以下のマークアップを追加します。

<div>
  <label for="SearchTitle">Movie title contains the following:</label>
  <input type="text" name="searchTitle" value="@Request.QueryString["searchTitle"]" />
  <input type="Submit" value="Search Title" /><br/>
</div>

この検索を処理するコードは、ジャンル検索のコードと似ていますが、LIKE 検索を組み立てる必要がある点が異なります。 ページ最上部のコード ブロック内に、ジャンル検索の if ブロックに続けて、以下の if ブロックを追加します。

if(!Request.QueryString["searchTitle"].IsEmpty() ) {
    selectCommand = "SELECT * FROM Movies WHERE Title LIKE @0";
    searchTerm = "%" + Request["searchTitle"] + "%";
}

検索に LIKE 演算子を使用し、検索語の前と後に "%" を付けていることを除けば、このコードのロジックは以前に示したものと同じです。

以上のとおり、ページに検索をもう 1 つ追加することは非常に簡単です。 必要な作業は以下のことだけでした。

  • 対応する検索ボックスに値が含まれているかどうかをテストする if ブロックを作成します。
  • selectCommand 変数に、新しい SQL ステートメントを設定します。
  • searchTerm 変数に、クエリに渡す値を設定します。

タイトル検索の新しいロジックを含めたコード ブロック全体は以下のようになります。

@{
    var db = Database.Open("WebPagesMovies") ;
    var selectCommand = "SELECT * FROM Movies";
    var searchTerm = "";

    if(!Request.QueryString["searchGenre"].IsEmpty() ) {
        selectCommand = "SELECT * FROM Movies WHERE Genre = @0";
        searchTerm = Request.QueryString["searchGenre"];
    }

   if(!Request.QueryString["searchTitle"].IsEmpty() ) {
        selectCommand = "SELECT * FROM Movies WHERE Title LIKE @0";
        searchTerm = "%" + Request["searchTitle"] + "%";
    }

    var selectedData = db.Query(selectCommand, searchTerm);
    var grid = new WebGrid(source: selectedData, defaultSort: "Genre", rowsPerPage:8);
}

このコードの動作の概要は次のとおりです。

  • 冒頭で、変数 searchTermselectCommand を初期化します。 これらの変数には、ページ内で行われたユーザーの操作に基づき、適切な検索語 (ある場合) と適切な SQL コマンドを設定します。 既定の検索は、データベースからすべての映画を取得するという単純なケースです。
  • searchGenresearchTitle に関する 2 つのテストでは、searchTerm に検索語の値を設定します。 また、これらのコード ブロックでは、selectCommand に、その検索に適した SQL コマンドを設定します。
  • selectedCommand に格納されたいずれかの SQL コマンドと、searchTerm に格納された値を渡して、1 回だけ db.Query メソッドを呼び出します。 検索語がない場合 (ジャンルなし、タイトルに含まれる言葉もなしの場合)、searchTerm の値は空文字列です。 ただし、その場合はパラメーターを取らないクエリを使用するため、空であることは問題になりません。

タイトル検索機能をテストする

完成した検索ページのテストが可能になりました。 Movies.cshtmlを実行します。

ジャンルを入力し、[Search Genre (ジャンル検索)] をクリックします。 グリッドには、以前と同様にそのジャンルの映画が表示されます。

タイトルに含まれる言葉を入力し、[Search Title (タイトル検索)] をクリックします。 グリッドには、その言葉を含んだタイトルが付いている映画が表示されます。

Movies page listing after searching for 'The' in the title

両方のテキスト ボックスを空白にして、いずれかのボタンをクリックします。 グリッドにはすべての映画が表示されます。

クエリを組み合わせる

ここまでの段階では、実行できる検索機能はどちらか一方だけに制限されています。 たとえ、タイトル、ジャンルの両方の検索ボックスに値を入力しても、両方を同時に検索する機能はありません。 たとえば、タイトルに "Adventure" という言葉を含んだアクション映画をすべて探すことはできません (現時点で記述されているコードでは、ジャンルとタイトルの値を両方とも入力した場合、タイトル検索が優先されます)。両方の条件を組み合わせた検索機能を実現するには、以下のような構文の SQL クエリを作成する必要があると考えられます。

SELECT * FROM Movies WHERE Genre = @0 AND Title LIKE @1

また、クエリを実行するには、概して以下のようなステートメントを使用する必要があると考えられます。

var selectedData = db.Query(selectCommand, searchGenre, searchTitle);

これでわかるように、検索条件のさまざまな順列組み合わせに対応できるロジックを作成しようとすると、作業がやや複雑になります。 このチュートリアルでは、そこまでの内容は取り上げません。

この後の内容

この次のチュートリアルでは、データベースに映画を追加できるフォームを含んだページを作成します。

@{
    var db = Database.Open("WebPagesMovies") ;
    var selectCommand = "SELECT * FROM Movies";
    var searchTerm = "";

    if(!Request.QueryString["searchGenre"].IsEmpty() ) {
        selectCommand = "SELECT * FROM Movies WHERE Genre = @0";
        searchTerm = Request.QueryString["searchGenre"];
    }

    if(!Request.QueryString["searchTitle"].IsEmpty() ) {
        selectCommand = "SELECT * FROM Movies WHERE Title LIKE @0";
        searchTerm = "%" + Request["searchTitle"] + "%";
    }

    var selectedData = db.Query(selectCommand, searchTerm);
    var grid = new WebGrid(source: selectedData, defaultSort: "Genre", rowsPerPage:3);
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Movies</title>
    <style type="text/css">
      .grid { margin: 4px; border-collapse: collapse; width: 600px; }
      .grid th, .grid td { border: 1px solid #C0C0C0; padding: 5px; }
      .head { background-color: #E8E8E8; font-weight: bold; color: #FFF; }
      .alt { background-color: #E8E8E8; color: #000; }
    </style>
  </head>
  <body>
    <h1>Movies</h1>
      <form method="get">
        <div>
        <label for="searchGenre">Genre to look for:</label>
        <input type="text" name="searchGenre" value="@Request.QueryString["searchGenre"]" />
        <input type="Submit" value="Search Genre" /><br/>
        (Leave blank to list all movies.)<br/>
        </div>

        <div>
          <label for="SearchTitle">Movie title contains the following:</label>
          <input type="text" name="searchTitle" value="@Request.QueryString["searchTitle"]" />
          <input type="Submit" value="Search Title" /><br/>
        </div>
      </form>

    <div>
      @grid.GetHtml(
        tableStyle: "grid",
        headerStyle: "head",
        alternatingRowStyle: "alt",
        columns: grid.Columns(
          grid.Column("Title"),
          grid.Column("Genre"),
          grid.Column("Year")
        )
      )
    </div>
  </body>
</html>

その他のリソース