Model モデルに検証ロジックを追加する

作成者: Rick Anderson

Note

ASP.NET MVC 5 と Visual Studio 2013 を使用するこのチュートリアルの更新版は、こちらで入手できます。 より安全で、より指示がわかりやすく、より多くの機能が紹介されています。

このセクションでは、Movie モデルに検証ロジックを追加します。また、ユーザーがアプリケーションを使って映画を作成または編集しようとするたびに検証規則が確実に適用されるようにします。

DRY 原則を維持する

ASP.NET MVC の中心となる設計思想の 1 つは、DRY ("Don't Repeat Yourself") です。 ASP.NET MVC では、機能や動作を 1 回だけ指定し、それをアプリケーション内のすべての場所に反映することが奨励されます。 そうすることで、記述する必要のあるコードの量が減り、実際に記述するコードではエラーが発生しにくくなり、保守もしやすくなります。

ASP.NET MVC と Entity Framework Code First に用意されている検証サポートは、DRY 原則の実施を示す好例といえます。 検証規則を 1 つの場所 (モデル クラス内) で宣言的に指定すれば、アプリケーション内のすべての場所で適用されます。

映画アプリケーションでこの検証サポートを利用する方法を見てみましょう。

Movie モデルに検証規則を追加する

まず、Movie クラスにいくつかの検証ロジックを追加します。

Movie.cs ファイルを開きます。 ファイルの上部に、System.ComponentModel.DataAnnotations 名前空間を参照する using ステートメントを追加します。

using System.ComponentModel.DataAnnotations;

この名前空間には System.Web が含まれていないことに注意してください。 DataAnnotations には、任意のクラスまたはプロパティに宣言的に適用できる検証属性のセットが組み込みで用意されています。

次に、組み込みの RequiredStringLengthRange 検証属性を利用するように Movie クラスを更新します。 属性を適用する場所の例として、次のコードを使用してください。

public class Movie {
    public int ID { get; set; }

    [Required]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }

    [Required]
    public string Genre { get; set; }

    [Range(1, 100)]
    [DataType(DataType.Currency)]
    public decimal Price { get; set; }

    [StringLength(5)]
    public string Rating { get; set; }
}

アプリケーションを実行すると、次の実行時エラーが再び表示されます。

"データベースの作成後、'MovieDBContext' コンテキストの背後にあるモデルが変更されました。Code First Migrations を使用したデータベースの更新を検討してください (https://go.microsoft.com/fwlink/?LinkId=238269)。"

移行を使ってスキーマを更新します。 ソリューションをビルドした後、[パッケージ マネージャー コンソール] ウィンドウを開き、次のコマンドを入力します。

add-migration AddDataAnnotationsMig
update-database

このコマンドが完了すると、指定された名前 (AddDataAnnotationsMig) を持つ DbMigration の新しい派生クラスを定義したクラス ファイルが Visual Studio で開かれ、その Up メソッドを見ると、スキーマ制約を更新するコードが確認できます。 Title フィールドと Genre フィールドは Null 許容ではなくなり (つまり、値を入力する必要があります)、Rating フィールドの最大長は 5 になります。

検証属性では、適用対象のモデル プロパティに適用する動作を指定します。 Required 属性は、そのプロパティに値を指定する必要があることを示します。このサンプルの場合、ある映画が有効とみなされるためには TitleReleaseDateGenrePrice プロパティの値を指定する必要があります。 Range 属性は、指定した範囲内に値を制限します。 StringLength 属性では、文字列プロパティの最大長を設定でき、オプションとして最小長も設定できます。 組み込み型 (decimal, int, float, DateTime など) は既定で必須なので、Required 属性は必要ありません。

アプリケーションが変更をデータベースに保存する前に、Code First によってモデル クラスに対して指定した検証規則が適用されます。 たとえば、次のコードでは、SaveChanges メソッドが呼び出されたときに例外がスローされます。いくつかの必須の Movie プロパティ値が指定されておらず、料金が 0 (有効な範囲外) であるためです。

MovieDBContext db = new MovieDBContext();

Movie movie = new Movie();
movie.Title = "Gone with the Wind";
movie.Price = 0.0M;

db.Movies.Add(movie);  
db.SaveChanges();        // <= Will throw server side validation exception  

.NET Framework によって検証規則が自動的に適用されることで、アプリケーションがより堅牢になります。 また、ユーザーが何かを検証することを忘れてしまい、データベースに不適切なデータが誤って格納されることもなくなります。

次に、更新した Movie.cs ファイルの完全なコード リストを示します:

using System;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models {
    public class Movie {
        public int ID { get; set; }

        [Required]
        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }

        [Required]
        public string Genre { get; set; }

        [Range(1, 100)]
        [DataType(DataType.Currency)]
        public decimal Price { get; set; }

        [StringLength(5)]
        public string Rating { get; set; }
    }

    public class MovieDBContext : DbContext {
        public DbSet<Movie> Movies { get; set; }
    }
}

ASP.NET MVC の検証エラー UI

アプリケーションを再実行し、/Movies の URL に移動します。

[Create New] リンクをクリックして、新しい映画を追加します。 フォームにいくつかの無効な値を入力し、[Create] ボタンをクリックします。

8_validationErrors

Note

小数点にコンマ (「,」) を使用する英語以外のロケールで jQuery 検証をサポートするには、(https://github.com/jquery/globalize からの) globalize.js と特定の cultures/globalize.cultures.js ファイル、および Globalize.parseFloat を使用する JavaScript を含める必要があります。 以下のコードは、"fr-FR" カルチャに対応するように Views\Movies\Edit.cshtml ファイルに加えた変更を示しています。

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script src="~/Scripts/globalize.js"></script>
    <script src="~/Scripts/globalize.culture.fr-FR.js"></script>
    <script>
        $.validator.methods.number = function (value, element) {
            return this.optional(element) ||
                !isNaN(Globalize.parseFloat(value));
        }
        $(document).ready(function () {
            Globalize.culture('fr-FR');
        });
    </script>
    <script>
        jQuery.extend(jQuery.validator.methods, {    
            range: function (value, element, param) {        
                //Use the Globalization plugin to parse the value        
                var val = $.global.parseFloat(value);
                return this.optional(element) || (
                    val >= param[0] && val <= param[1]);
            }
        });

    </script>
}

フォームが自動的に赤い枠線の色を使って無効なデータを含むテキスト ボックスを強調表示し、それぞれの横に適切な検証エラー メッセージを出力していることに注目してください。 エラーは、(JavaScript と jQuery を使用している) クライアント側とサーバー側 (ユーザーが JavaScript を無効にしている場合) の両方に適用されます。

真の利点は、この検証 UI を有効にするために MoviesController クラスや Create.cshtml ビューのコードを 1 行も変更する必要がないということです。 このチュートリアルで前に作成したコントローラーとビューにより、Movie モデル クラスのプロパティで検証属性を使って指定した検証規則が自動的に取得されます。

Title プロパティと Genre プロパティについては、フォームを送信する ([Create] ボタンを押す) か、入力フィールドにテキストを入力して削除するまで、必須の属性が適用されないことに気付かれたかもしれません。 最初は空であるフィールド (Create ビューのフィールドなど) が、必須の属性のみを持っており他の検証属性は持っていない場合は、次の操作を実行すると検証をトリガーできます。

  1. Tab キーを押してフィールドに移動します。
  2. 何らかのテキストを入力します。
  3. タブを終了します。
  4. Tab キーを押してもう一度フィールドに移動します。
  5. テキストを削除します。
  6. タブを終了します。

上記の一連の操作を実行すると、送信ボタンを押さずに必須の検証をトリガーできます。 フィールドを 1 つも入力せずにそのまま送信ボタンを押すと、クライアント側の検証をトリガーできます。 クライアント側の検証エラーがなくなるまで、フォーム データはサーバーに送信されません。 これをテストするには、HTTP Post メソッドにブレークポイントを設定するか、fiddler ツールまたは IE 9 の F12 開発者ツールを使用します。

[M V C Movie Create]\(M V C ムービーの作成\) ページを示すスクリーンショット。タイトルの横にアラートが表示され、[タイトル] フィールドが必要であることが示されます。[ジャンル] の横にあるアラートは、[ジャンル] フィールドが必要であることを示します。

Create ビューと Create アクション メソッドで検証が発生するしくみ

コントローラーまたはビューのコードを更新しなくても検証 UI が生成する仕組みが気になるかもしれません。 次のリストは、MovieController クラス内の Create メソッドの外観を示しています。 これは、このチュートリアルで前に作成したときから変わっていません。

//
// GET: /Movies/Create

public ActionResult Create()
{
    return View();
}

//
// POST: /Movies/Create

[HttpPost]
public ActionResult Create(Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Movies.Add(movie);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(movie);
}

最初の (HTTP GET の) Create アクション メソッドは、初期の作成フォームを表示します。 2 番目の ([HttpPost]) バージョンは、フォームの送信を処理します。 2 番目の Create メソッド (HttpPost バージョン) は、ModelState.IsValid を呼び出してムービーに検証エラーがあるかどうかを確認します。 このメソッドを呼び出すと、オブジェクトに適用されているすべての検証属性が評価されます。 オブジェクトに検証エラーがある場合、Create メソッドはフォームを再表示します。 エラーがない場合、メソッドはデータベースに新しいムービーを保存します。 使用しているムービーの例では、 クライアント側で検証エラーが検出された場合、フォームはサーバーにポストされません。2 番目 Createmethod は呼び出されません。 ブラウザーで JavaScript を無効にすると、クライアントの検証が無効になり、HTTP POST の Create メソッドで ModelState.IsValid が呼び出され、映画に検証エラーがあるかどうかがチェックされます。

HttpPost Create メソッドにブレークポイントを設定し、メソッドが呼び出されないことを確認できます。検証エラーが検出された場合、クライアント側の検証はフォームのデータを送信しません。 ブラウザーで JavaScript を無効にすると、エラーのあるフォームが送信され、ブレークポイントがヒットします。 JavaScript がなくても完全な検証が行われます。 次の図は、Internet Explorer で JavaScript を無効にする方法を示しています。

[セキュリティ] タブが開いている [インターネット オプション] ウィンドウを示すスクリーンショット。カスタム レベルは赤で囲まれています。[セキュリティ設定] ウィンドウで、アクティブなスクリプトが無効に設定されています。スクロール バーは赤で囲まれています。

H t t p の投稿を示すスクリーンショット。[Model State]\(モデルの状態\) ドットが [有効] の場合は強調表示されます。

次の図では、FireFox ブラウザーで JavaScript を無効にする方法を示します。

[オプション] ウィンドウを示すスクリーンショット。[コンテンツ] が選択され、[Java スクリプトを有効にする] がオンになっています。

次の図は、Chrome ブラウザーで JavaScript を無効にする方法を示しています。

[オプション] ページを示すスクリーンショット。フードの下が選択され、赤で囲まれています。[コンテンツ設定] で、[Java スクリプト] が [すべてのサイトで Java スクリプトの実行を許可する] に設定されています。

次に、このチュートリアルで前にスキャフォールディングした Create.cshtml ビュー テンプレートを示します。 これは、前に示した両方のアクション メソッドで、初期フォームの表示と、エラー発生時のフォームの再表示に使われます。

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"></script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Movie</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.ReleaseDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.ReleaseDate)
            @Html.ValidationMessageFor(model => model.ReleaseDate)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Genre)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Genre)
            @Html.ValidationMessageFor(model => model.Genre)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Price)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Price)
            @Html.ValidationMessageFor(model => model.Price)
        </div>
        <div class="editor-label">
    @Html.LabelFor(model => model.Rating)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.Rating)
    @Html.ValidationMessageFor(model => model.Rating)
</div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

このコードで、Html.EditorFor ヘルパーを使用して各 Movie プロパティの <input> 要素を出力している方法に注目してください。 このヘルパーの隣では、Html.ValidationMessageFor ヘルパー メソッドを呼び出しています。 これら 2 つのヘルパー メソッドでは、コントローラーからビューに渡されるモデル オブジェクト (この場合は Movie オブジェクト) が使用されます。 これらは、モデルで指定されている検証属性を自動的に探し、必要に応じてエラー メッセージを表示します。

この方法が非常に優れている点は、コントローラーも Create ビュー テンプレートも、適用される実際の検証規則や表示される特定のエラー メッセージについては何も知らないということです。 検証規則とエラー文字列は、Movie クラスでのみ指定されています。 これらの同じ検証規則が、Edit ビューや、モデルを編集する他のユーザー作成のビュー テンプレートにも自動的に適用されます。

後で検証ロジックを変更する必要があるときは、モデル (この例では movie クラス) に検証属性を追加するだけでそれを行うことができます。 アプリケーションの異なる部分で規則の適用方法が一貫しない可能性を心配する必要はありません。すべての検証ロジックは 1 か所で定義され、すべての場所で使われます。 これにより、コードの簡潔さが保たれ、簡単に維持や更新できます。 また、これは DRY 原則に完全に従うことを意味します。

Movie モデルに書式設定を追加する

Movie.cs ファイルを開き、Movie クラスを調べます。 System.ComponentModel.DataAnnotations 名前空間には、組み込みの検証属性セットに加え、書式設定の属性もあります。 リリース日と価格のフィールドには、DataType 列挙値が既に適用されています。 次のコードでは、適切な DisplayFormat 属性が設定された ReleaseDate プロパティと Price プロパティを示します。

[DataType(DataType.Date)] 
public DateTime ReleaseDate { get; set; }

[DataType(DataType.Currency)] 
public decimal Price { get; set; }

DataType 属性は検証属性ではなく、HTML のレンダリング方法をビュー エンジンに指示するために使用します。 上の例では、DataType.Date 属性により、映画の日付のみが表示され、時刻は表示されません。 たとえば、次の DataType 属性はデータの書式を検証しません。

[DataType(DataType.EmailAddress)]
[DataType(DataType.PhoneNumber)]
[DataType(DataType.Url)]

上記の属性は、ビュー エンジンに対して、データの書式設定のヒントを提供するだけです (<a> (URL の場合) や <a href="mailto:EmailAddress.com"> (電子メールの場合) などの属性を提供します)。 RegularExpression 属性を使って、データの書式を検証することができます。

DataType 属性を使う別の方法として、DataFormatString 値を明示的に設定することもできます。 次のコードは、リリース日のプロパティと日付の書式指定文字列 (つまり "d") を示しています。 これを使うと、時刻をリリース日に含めないように指定することができます。

[DisplayFormat(DataFormatString = "{0:d}")]
public DateTime ReleaseDate { get; set; }

完全な Movie クラスを次に示します。

public class Movie {
    public int ID { get; set; }

    [Required]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }

    [Required]
    public string Genre { get; set; }

    [Range(1, 100)]
    [DataType(DataType.Currency)]
    public decimal Price { get; set; }

    [StringLength(5)]
    public string Rating { get; set; }
}

アプリケーションを実行し、Movies コントローラーを参照します。 リリース日と料金は適切に書式設定されます。 次の図は、カルチャとして "fr-FR" を使用したリリース日と料金を示しています。

8_format_SM

次の図は、既定のカルチャ (英語 (米国)) で同じデータを表示したものです。

4 つの映画が一覧表示されている [M V C Movie Index]\(M V C ムービー インデックス\) ページを示すスクリーンショット。

このシリーズの次のパートでは、アプリケーションを確認し、自動的に生成される Details および Delete メソッドに対していくつかの改良を行います。