検証の追加
作成者: Rick Anderson
Note
Visual Studio の最新バージョンを使用した、このチュートリアルの更新バージョンはこちらにあります。 新しいチュートリアル (このチュートリアルよりも多くの改善がされています) では、ASP.NET Core MVC を使用しています。
このチュートリアルでは、ASP.NET Core MVC のコントローラーとビューについて説明します。 Razor Pages は ASP.NET Core での新しい代替手段であり、Web UI の構築をより簡単かつ生産的にする、ページベースのプログラミング モデルです。 MVC のバージョンの前に、Razor ページのチュートリアルを試すことをお勧めします。 この Razor ページのチュートリアルの特徴は次のとおりです。
- 使いやすい。
- 多くの機能をカバーしている。
- 新しいアプリ開発には、これが最適のアプローチです。
このセクションでは、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
名前空間には System.Web
が含まれていないことに注意してください。 DataAnnotations には、任意のクラスまたはプロパティに宣言的に適用できる検証属性のセットが組み込みで用意されています。 (また、書式設定を支援し、検証を行わない DataType のような書式設定属性もあります)。
次に、組み込みの Required
、StringLength
、RegularExpression、Range
検証属性を利用するように Movie
クラスを更新します。 Movie
クラスを次で置き換えます。
public class Movie
{
public int ID { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
[StringLength(5)]
public string Rating { get; set; }
}
StringLength
属性は文字列の最大長を設定し、データベースにこの制限を設定するため、データベース スキーマが変更されます。 サーバー エクスプローラーで Movies テーブルを右クリックし、[テーブル定義を開く] を選択します。
上の画像では、すべての文字列フィールドが NVARCHAR (MAX) に設定されていることがわかります。 移行を使ってスキーマを更新します。 ソリューションをビルドした後、[パッケージ マネージャー コンソール] ウィンドウを開き、次のコマンドを入力します。
add-migration DataAnnotations
update-database
このコマンドが完了すると、指定された名前 (DataAnnotations
) を持つ DbMigration
の新しい派生クラスを定義したクラス ファイルが Visual Studio で開かれ、その Up
メソッドを見ると、スキーマ制約を更新するコードが確認できます。
public override void Up()
{
AlterColumn("dbo.Movies", "Title", c => c.String(maxLength: 60));
AlterColumn("dbo.Movies", "Genre", c => c.String(nullable: false, maxLength: 30));
AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5));
}
Genre
フィールドは Null 許容ではなくなりました (つまり、値を入力する必要があります)。 Rating
フィールドの最大長は 5 で、Title
の最大長は 60 です。 Title
の最小長の 3 と Price
の範囲によってスキーマの変更は作成されませんでした。
Movie スキーマを確認します。
文字列フィールドには新しい長さ制限が表示され、Genre
の Null 許容はオンではなくなっています。
検証属性では、適用対象のモデル プロパティに適用する動作を指定します。 Required
および MinimumLength
属性は、プロパティに値が必要であることを示します。ただし、この検証を満たすためにユーザーが空白を入力することは禁止されていません。 RegularExpression 属性は、入力できる文字を制限するために使用されます。 上記のコードで、Genre
と Rating
は、文字のみを使用する必要があります (空白、数字、特殊文字は使用できません)。 Range
属性は、指定した範囲内に値を制限します。 StringLength
属性では、文字列プロパティの最大長を設定でき、オプションとして最小長も設定できます。 値の型 (decimal, int, float, DateTime
など) はもともと必須であり、Required
属性を必要としません。
アプリケーションが変更をデータベースに保存する前に、Code First によってモデル クラスに対して指定した検証規則が適用されます。 たとえば、次のコードでは、SaveChanges
メソッドが呼び出されたときに DbEntityValidationException 例外がスローされます。いくつかの必須の Movie
プロパティ値がないためです。
MovieDBContext db = new MovieDBContext();
Movie movie = new Movie();
movie.Title = "Gone with the Wind";
db.Movies.Add(movie);
db.SaveChanges(); // <= Will throw server side validation exception
上記のコードは、次の例外をスローします。
1 つ以上のエンティティの検証に失敗しました。 詳細については、'EntityValidationErrors' プロパティを参照してください。
.NET Framework によって検証規則が自動的に適用されることで、アプリケーションがより堅牢になります。 また、ユーザーが何かを検証することを忘れてしまい、データベースに不適切なデータが誤って格納されることもなくなります。
ASP.NET MVC の検証エラー UI
アプリケーションを実行し、/Movies URL に移動します。
[Create New] リンクをクリックして、新しい映画を追加します。 フォームに無効な値をいくつか入力します。 jQuery クライアント側の検証でエラーが検出されるとすぐに、エラー メッセージが表示されます。
Note
小数点にコンマ (",") を使用する英語以外のロケールの jQuery 検証をサポートするには、このチュートリアルで前述したように NuGet globalize を含める必要があります。
フォームが自動的に赤い枠線の色を使って無効なデータを含むテキスト ボックスを強調表示し、それぞれの横に適切な検証エラー メッセージを出力していることに注目してください。 エラーは、(JavaScript と jQuery を使用している) クライアント側とサーバー側 (ユーザーが JavaScript を無効にしている場合) の両方に適用されます。
真の利点は、この検証 UI を有効にするために MoviesController
クラスや Create.cshtml ビューのコードを 1 行も変更する必要がないということです。 このチュートリアルで前に作成したコントローラーとビューにより、Movie
モデル クラスのプロパティで検証属性を使って指定した検証規則が自動的に取得されます。 Edit
アクション メソッドを使って検証をテストします。同じ検証が適用されます。
クライアント側の検証エラーがなくなるまで、フォーム データはサーバーに送信されません。 このことは、Fiddler ツールまたは IE F12 開発者ツールを使って HTTP Post メソッドにブレークポイントを設定することにより確認できます。
Create ビューと Create アクション メソッドで検証が発生するしくみ
コントローラーまたはビューのコードを更新しなくても検証 UI が生成する仕組みが気になるかもしれません。 次のリストは、MovieController
クラス内の Create
メソッドの外観を示しています。 これは、このチュートリアルで前に作成したときから変わっていません。
public ActionResult Create()
{
return View();
}
// POST: /Movies/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")] 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>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
</div>
@*Fields removed for brevity.*@
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
このコードで、Html.EditorFor
ヘルパーを使用して各 Movie
プロパティの <input>
要素を出力している方法に注目してください。 このヘルパーの隣では、Html.ValidationMessageFor
ヘルパー メソッドを呼び出しています。 これら 2 つのヘルパー メソッドでは、コントローラーからビューに渡されるモデル オブジェクト (この場合は Movie
オブジェクト) が使用されます。 これらは、モデルで指定されている検証属性を自動的に探し、必要に応じてエラー メッセージを表示します。
この方法の非常によい点は、コントローラーも Create
ビュー テンプレートも、適用される実際の検証規則や、表示される特定のエラー メッセージについて、何も知らないことです。 検証規則とエラー文字列は、Movie
クラスでのみ指定されています。 同じ検証規則が、Edit
ビューおよびモデルを編集する他のユーザー作成のビュー テンプレートに、自動的に適用されます。
後で検証ロジックを変更する必要があるときは、モデル (この例では movie
クラス) に検証属性を追加するだけでそれを行うことができます。 アプリケーションの異なる部分で規則の適用方法が一貫しない可能性を心配する必要はありません。すべての検証ロジックは 1 か所で定義され、すべての場所で使われます。 これにより、コードの簡潔さが保たれ、簡単に維持や更新できます。 また、これは DRY 原則に完全に従うことを意味します。
DataType 属性の使用
Movie.cs ファイルを開き、Movie
クラスを調べます。 System.ComponentModel.DataAnnotations
名前空間には、組み込みの検証属性セットに加え、書式設定の属性もあります。 リリース日と価格のフィールドには、DataType
列挙値が既に適用されています。 次のコードでは、適切な DataType
属性が設定された ReleaseDate
プロパティと Price
プロパティを示します。
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[DataType(DataType.Currency)]
public decimal Price { get; set; }
DataType 属性は、ビュー エンジンに対して、データの書式設定のヒントのみを提供します (また、URL の場合に <a>
、電子メールの場合に <a href="mailto:EmailAddress.com">
などの属性を提供します)。 RegularExpression 属性を使って、データの書式を検証することができます。 DataType 属性は、データベースの組み込み型よりも具体的なデータ型を指定するために使用されます。これらは検証属性では "ありません"。 この例では、追跡する必要があるのは、日付と時刻ではなく、日付のみです。 DataType 列挙型は、Date、Time、PhoneNumber、Currency、EmailAddress など、多くのデータ型に対応しています。 また、DataType
属性を使用して、アプリケーションで型固有の機能を自動的に提供することもできます。 たとえば、DataType.EmailAddress に対して mailto:
リンクを作成したり、HTML5 をサポートするブラウザーで DataType.Date に日付セレクターを提供したりできます。 DataType 属性は、HTML 5 ブラウザーが認識できる HTML 5 の data- ("データ ダッシュ" と読みます) 属性を出力します。 DataType 属性では、どのような検証も提供されません。
DataType.Date
は、表示される日付の書式を指定しません。 既定では、データ フィールドはサーバーの CultureInfo に基づく既定の書式に従って表示されます。
DisplayFormat
属性は、日付の書式を明示的に指定するために使用されます。
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
ApplyFormatInEditMode
設定は、値が編集用のテキスト ボックスに表示されるときも、指定されている書式設定を適用する必要があることを指定します。 (フィールドによっては適用したくないこともあります。たとえば、通貨値では、編集用テキスト ボックスには通貨記号が必要でない場合があります)。
DisplayFormat 属性を単独で使用できますが、一般に DataType 属性も使うことをお勧めします。 DataType
属性では、画面でのレンダリング方法ではなく、データの "セマンティクス" が伝達され、DisplayFormat
では得られない次のような利点があります。
- ブラウザーで HTML5 の機能を有効にできます (たとえば、カレンダー コントロール、ロケールに適した通貨記号、メール リンクの表示など)。
- 既定で、ロケールに基づく正しい書式を使ってデータがブラウザーにレンダリングされます。
- DataType 属性を使うと、MVC はデータをレンダリングするための正しいフィールド テンプレートを選択できます (DisplayFormat だけを使うと、文字列テンプレートが使われます)。 詳しくは、Brad Wilson の ASP.NET MVC 2 テンプレートに関するページをご覧ください。 (この記事は、MVC 2 に関して書かれていますが、ASP.NET MVC の現在のバージョンにも当てはまります)。
日付フィールドで DataType
属性を使う場合は、Chrome ブラウザーでフィールドが正しくレンダリングされるようにするには、DisplayFormat
属性も指定する必要があります。 詳しくは、StackOverflow のこちらのスレッドをご覧ください。
Note
jQuery の検証は、Range 属性と DateTime では機能しません。 たとえば、次のコードでは、指定した範囲内の日付であっても、クライアント側の検証エラーが常に表示されます。
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
DateTime で Range 属性を使うには、jQuery の日付検証を無効にする必要があります。 一般的に、モデルに日付をハードコーディングしてコンパイルすることはお勧めできません。そのため、Range 属性と DateTime の使用は推奨されません。
次のコードは、1 行で複数の属性を組み合わせる例です。
public class Movie
{
public int ID { get; set; }
[Required,StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Release Date"),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; }
[Required,StringLength(5)]
public string Rating { get; set; }
}
このシリーズの次のパートでは、アプリケーションを確認し、自動的に生成される Details
および Delete
メソッドに対していくつかの改良を行います。