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 には、任意のクラスまたはプロパティに宣言的に適用できる検証属性のセットが組み込みで用意されています。
次に、組み込みの Required
、StringLength
、Range
検証属性を利用するように 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
属性は、そのプロパティに値を指定する必要があることを示します。このサンプルの場合、ある映画が有効とみなされるためには Title
、ReleaseDate
、Genre
、Price
プロパティの値を指定する必要があります。 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] ボタンをクリックします。
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 ビューのフィールドなど) が、必須の属性のみを持っており他の検証属性は持っていない場合は、次の操作を実行すると検証をトリガーできます。
- Tab キーを押してフィールドに移動します。
- 何らかのテキストを入力します。
- タブを終了します。
- Tab キーを押してもう一度フィールドに移動します。
- テキストを削除します。
- タブを終了します。
上記の一連の操作を実行すると、送信ボタンを押さずに必須の検証をトリガーできます。 フィールドを 1 つも入力せずにそのまま送信ボタンを押すと、クライアント側の検証をトリガーできます。 クライアント側の検証エラーがなくなるまで、フォーム データはサーバーに送信されません。 これをテストするには、HTTP Post メソッドにブレークポイントを設定するか、fiddler ツールまたは IE 9 の F12 開発者ツールを使用します。
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 番目 Create
method は呼び出されません。 ブラウザーで JavaScript を無効にすると、クライアントの検証が無効になり、HTTP POST の Create
メソッドで ModelState.IsValid
が呼び出され、映画に検証エラーがあるかどうかがチェックされます。
HttpPost Create
メソッドにブレークポイントを設定し、メソッドが呼び出されないことを確認できます。検証エラーが検出された場合、クライアント側の検証はフォームのデータを送信しません。 ブラウザーで JavaScript を無効にすると、エラーのあるフォームが送信され、ブレークポイントがヒットします。 JavaScript がなくても完全な検証が行われます。 次の図は、Internet Explorer で JavaScript を無効にする方法を示しています。
次の図では、FireFox ブラウザーで JavaScript を無効にする方法を示します。
次の図は、Chrome ブラウザーで JavaScript を無効にする方法を示しています。
次に、このチュートリアルで前にスキャフォールディングした 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" を使用したリリース日と料金を示しています。
次の図は、既定のカルチャ (英語 (米国)) で同じデータを表示したものです。
このシリーズの次のパートでは、アプリケーションを確認し、自動的に生成される Details
および Delete
メソッドに対していくつかの改良を行います。