モデルに検証を追加する (C#)

作成者: Rick Anderson

Note

ASP.NET MVC 5 と Visual Studio 2013 を使用するこのチュートリアルの更新バージョンは、こちらで入手できます。 より安全で、より簡単に操作でき、より多くの機能を備えています。

このチュートリアルでは、Microsoft Visual Web Developer 2010 Express Service Pack 1 (Microsoft Visual Studio の無料バージョン) を使用した ASP.NET MVC Web アプリケーション構築の基本事項を説明します。 開始する前に、以下に示す前提条件がインストールされていることを確認してください。 Web Platform Installer のリンクをクリックすると、これらをすべてインストールできます。 また、次のリンクを使用して前提条件となるソフトウェアを個別にインストールすることもできます。

Visual Web Developer 2010 ではなく Visual Studio 2010 を使用する場合は、Visual Studio 2010 の前提条件のリンクをクリックして、前提条件をインストールします。

このトピックに関連する、Visual Web Developer プロジェクトと C# ソース コードを使用できます。 C# バージョンをダウンロードします。 Visual Basic を使用する場合は、このチュートリアルの Visual Basic バージョンに切り替えてください。

このセクションでは、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;

この名前空間は、.NET Framework の一部です。 これにより、任意のクラスまたはプロパティに宣言的に適用できる組み込みの検証属性のセットが提供されます。

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

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

    [Required(ErrorMessage = "Title is required")]
    public string Title { get; set; }

    [Required(ErrorMessage = "Date is required")]
    public DateTime ReleaseDate { get; set; }

    [Required(ErrorMessage = "Genre must be specified")]
    public string Genre { get; set; }

    [Range(1, 100, ErrorMessage = "Price must be between $1 and $100")]
    public decimal Price { get; set; }

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

検証属性では、適用対象のモデル プロパティに適用する動作を指定します。 Required 属性は、プロパティに値が設定されている必要があることを示しています。このサンプルの場合、あるムービーが有効とみなされるためには TitleReleaseDateGenrePrice プロパティに値が設定されている必要があります。 Range 属性は、指定した範囲内に値を制限します。 StringLength 属性では、文字列プロパティの最大長を設定でき、オプションとして最小長も設定できます。

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

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 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(ErrorMessage = "Title is required")]
        public string Title { get; set; }

        public DateTime ReleaseDate { get; set; }

        [Required(ErrorMessage = "Genre must be specified")]
        public string Genre { get; set; }

        [Range(1, 100, ErrorMessage = "Price must be between $1 and $100")]
        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 Movie] リンクをクリックして、新しいムービーを追加します。 フォームにいくつかの無効な値を入力し、[Create] ボタンをクリックします。

8_validationErrors

フォームが自動的に無効なデータを含むテキスト ボックスを背景色によって強調表示し、それぞれの横に適切な検証エラー メッセージを出力していることに注目してください。 エラー メッセージは、Movie クラスに注釈を付けたときに指定したエラー文字列と一致しています。 エラーは、(JavaScript を使用している) クライアント側とサーバー側 (ユーザーが JavaScript を無効にしている場合) の両方に適用されます。

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

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);
}

最初のアクション メソッドは、初期の Create フォームを表示します。 2 番目は、フォームの送信を処理します。 2 番目の Create メソッドは ModelState.IsValid を呼び出してムービーに検証エラーがあるかどうかを確認します。 このメソッドを呼び出すと、オブジェクトに適用されているすべての検証属性が評価されます。 オブジェクトに検証エラーがある場合、Create メソッドはフォームを再表示します。 エラーがない場合、メソッドはデータベースに新しいムービーを保存します。

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

@model MvcMovie.Models.Movie
@{
    ViewBag.Title = "Create";
}
<h2>
    Create</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></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 クラスでのみ指定されています。

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

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

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

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

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

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

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

次のコードは、Price プロパティを通貨として書式設定しています。

[DisplayFormat(DataFormatString = "{0:c}")]
public decimal Price { get; set; }

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

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

    [Required(ErrorMessage = "Title is required")]
    public string Title { get; set; }

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

    [Required(ErrorMessage = "Genre must be specified")]
    public string Genre { get; set; }

    [Range(1, 100, ErrorMessage = "Price must be between $1 and $100")]
    [DisplayFormat(DataFormatString = "{0:c}")]
    public decimal Price { get; set; }

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

アプリケーションを実行し、Movies コントローラーを参照します。

8_format_SM

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