繰り返し #3 – フォーム検証を追加する (C#)

提供元: Microsoft

コードのダウンロード

3 番目のイテレーションでは、基本的なフォームの検証を追加します。 必須のフォーム フィールドを入力しないとユーザーがフォームを送信できないようにします。 また、メール アドレスと電話番号も検証します。

連絡先管理の ASP.NET MVC アプリケーションをビルドする (C#)

この一連のチュートリアルでは、連絡先管理アプリケーション全体を最初から最後までビルドします。 連絡先マネージャー アプリケーションを使用すると、人物の一覧に連絡先情報 (名前、電話番号、電子メール アドレス) を保存できます。

複数のイテレーションを通してアプリケーションをビルドします。 イテレーションのたびに、アプリケーションを徐々に改善します。 複数のイテレーションからなるこのアプローチの目的は、お客様が各変更の理由を理解できるようにすることです。

  • イテレーション #1 - アプリケーションを作成します。 最初のイテレーションでは、可能な限り簡単な方法で連絡先マネージャーを作成します。 基本的なデータベース操作 (生成、読み取り、更新、削除 (CRUD)) のサポートを追加します。

  • イテレーション #2 - アプリケーションの外観を良くします。 このイテレーションでは、既定の ASP.NET MVC ビュー マスター ページとカスケード スタイル シートを変更することで、アプリケーションの外観を向上させます。

  • イテレーション #3 - フォームの検証を追加します。 3 番目のイテレーションでは、基本的なフォームの検証を追加します。 必須のフォーム フィールドを入力しないとユーザーがフォームを送信できないようにします。 また、メール アドレスと電話番号も検証します。

  • イテレーション #4 - アプリケーションを疎結合します。 この 4 番目のイテレーションでは、いくつかのソフトウェア デザイン パターンを利用して、連絡先マネージャー アプリケーションを簡単に維持し、変更できるようにします。 たとえば、Repository パターンと Dependency Injection パターンを使用するようにアプリケーションをリファクターします。

  • イテレーション #5 - 単体テストを作成します。 5 番目のイテレーションでは、単体テストを追加することで、アプリケーションを簡単に維持し、変更できるようにします。 データ モデル クラスをモックし、コントローラーと検証ロジックの単体テストをビルドします。

  • イテレーション #6 - テスト駆動開発を使用します。 この 6 番目のイテレーションでは、最初に単体テストを記述し、この単体テストに対してコードを記述することにより、新しい機能をアプリケーションに追加します。 このイテレーションでは、連絡先グループを追加します。

  • イテレーション #7 - Ajax 機能を追加します。 7 番目のイテレーションでは、Ajax のサポートを追加することで、アプリケーションの応答性とパフォーマンスを向上させます。

このイテレーション

Contact Manager アプリケーションのこの 2 回目のイテレーションでは、基本的なフォームの検証を追加します。 必須のフォーム フィールドの値を入力しないとユーザーが連絡先を送信できないようにします。 電話番号とメール アドレスも検証します (図 1 を参照)。

The New Project dialog box

図 01: 検証を備えたフォーム (クリックするとフルサイズの画像が表示されます)

このイテレーションでは、検証ロジックをコントローラー アクションに直接追加します。 一般に、これは、ASP.NET MVC アプリケーションに検証を追加するのに推奨される方法ではありません。 より適切なアプローチは、アプリケーションの検証ロジックを個別のサービス レイヤー内に配置することです。 次のイテレーションでは、Contact Manager アプリケーションをリファクターして、アプリケーションをより維持しやすくします。

このイテレーションでは、物事をシンプルにするために、すべての検証コードを手動で記述します。 検証コードを自分で記述する代わりに、検証フレームワークを活用できます。 たとえば、Microsoft Enterprise Library Validation Application Block (VAB) を使用して、ASP.NET MVC アプリケーションの検証ロジックを実装できます。 Validation Application Block の詳細については、次を参照してください。

http://msdn.microsoft.com/library/dd203099.aspx

Create ビューへの検証の追加

まず、Create ビューに検証ロジックを追加します。 さいわい、Visual Studio を使用して Create ビューを生成したため、この Create ビューには検証メッセージを表示するために必要なすべてのユーザー インターフェイス ロジックが既に含まれています。 この Create ビューはリスト 1 の中に含まれています。

リスト 1 - \Views\Contact\Create.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ContactManager.Models.Contact>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<title>Create</title>
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <%= Html.ValidationSummary() %>

    <% using (Html.BeginForm()) {%>

        <fieldset class="fields">
            <legend>Create New Contact</legend>
            <p>
                <label for="FirstName">First Name:</label>
                <%= Html.TextBox("FirstName") %>
                <%= Html.ValidationMessage("FirstName", "*") %>
            </p>
            <p>
                <label for="LastName">Last Name:</label>
                <%= Html.TextBox("LastName") %>
                <%= Html.ValidationMessage("LastName", "*") %>
            </p>
            <p>
                <label for="Phone">Phone:</label>
                <%= Html.TextBox("Phone") %>
                <%= Html.ValidationMessage("Phone", "*") %>
            </p>
            <p>
                <label for="Email">Email:</label>
                <%= Html.TextBox("Email") %>
                <%= Html.ValidationMessage("Email", "*") %>
            </p>
            <p class="submit">
                <input type="submit" value="Create" />
            </p>
        </fieldset>

    <% } %>

</asp:Content>

HTML フォームのすぐ上に表示される Html.ValidationSummary() ヘルパー メソッドの呼び出しに注意してください。 検証エラー メッセージがある場合、このメソッドは箇条書きで検証メッセージを表示します。

さらに、各フォーム フィールドの横に表示される Html.ValidationMessage() の呼び出しにも注意してください。 ValidationMessage() ヘルパーは、個々の検証エラー メッセージを表示します。 リスト 1 のケースでは、検証エラーが存在する場合、アスタリスクが表示されます。

最後に、Html.TextBox() ヘルパーは、そのヘルパーによって表示されたプロパティに関連付けられた検証エラーが存在する場合、カスケード スタイル シート クラスを自動的にレンダリングします。 Html.TextBox() ヘルパーは、input-validation-error という名前のクラスをレンダリングします。

新しい ASP.NET MVC アプリケーションを作成すると、Site.css という名前のスタイル シートが Content フォルダー内に自動的に作成されます。 このスタイル シートには、検証エラー メッセージの外観に関連する以下の CSS クラスの定義が含まれています。

.field-validation-error
{
    color: #ff0000;
}

.input-validation-error
{
    border: 1px solid #ff0000;
    background-color: #ffeeee;
}

.validation-summary-errors
{
    font-weight: bold;
    color: #ff0000;
}

field-validation-error クラスは、Html.ValidationMessage() ヘルパーによってレンダリングされる、出力のスタイルを設定するために使用されます。 input-validation-error クラスは、Html.TextBox() ヘルパーによってレンダリングされる、テキスト ボックス (入力) のスタイルを設定するために使用されます。 validation-summary-errors クラスは、Html.ValidationSummary() ヘルパーによってレンダリングされる、順序指定されていないリストのスタイルを設定するために使用されます。

Note

このセクションで説明するスタイル シート クラスを変更して、検証エラー メッセージの外観をカスタマイズできます。

Create アクションへの検証ロジックの追加

現時点では、メッセージを生成するロジックを記述していないため、この Create ビューに検証エラー メッセージは表示されません。 検証エラー メッセージを表示するには、ModelState にエラー メッセージを追加する必要があります。

Note

UpdateModel() メソッドは、フォーム フィールドの値をプロパティに割り当てる際にエラーが発生した場合、ModelState にエラー メッセージを自動的に追加します。 たとえば、DateTime 値を受け取る BirthDate プロパティに文字列 "apple" を割り当てようとすると、UpdateModel() メソッドは ModelState にエラーを追加します。

リスト 2 の中の変更された Create() メソッドには、新しい連絡先がデータベースに挿入される前に Contact クラスのプロパティを検証する新しいセクションが含まれています。

リスト 2 - Controllers\ContactController.cs (検証を使用して作成)

//
// POST: /Contact/Create

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
{
    // Validation logic
    if (contactToCreate.FirstName.Trim().Length == 0)
        ModelState.AddModelError("FirstName", "First name is required.");
    if (contactToCreate.LastName.Trim().Length == 0)
        ModelState.AddModelError("LastName", "Last name is required.");
    if (contactToCreate.Phone.Length > 0 && !Regex.IsMatch(contactToCreate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
        ModelState.AddModelError("Phone", "Invalid phone number.");
    if (contactToCreate.Email.Length > 0 && !Regex.IsMatch(contactToCreate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
        ModelState.AddModelError("Email", "Invalid email address.");
    if (!ModelState.IsValid)
        return View();

    // Database logic
    try
    {
        _entities.AddToContactSet(contactToCreate);
        _entities.SaveChanges();
        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

この検証セクションでは、次の 4 つの異なる検証規則が適用されます。

  • FirstName プロパティの長さは 0 より大きくする必要があります (かつスペースのみで構成することはできません)
  • LastName プロパティの長さは 0 より大きくする必要があります (かつスペースのみで構成することはできません)
  • Phone プロパティに値 (長さが 0 より大きい) がある場合、Phone プロパティは正規表現と一致する必要があります。
  • Email プロパティに値 (長さが 0 より大きい) がある場合、Email プロパティは正規表現と一致する必要があります。

検証規則違反がある場合は、AddModelError() メソッドを使用して、エラー メッセージを ModelState に追加します。 ModelState にメッセージを追加する場合は、プロパティの名前と検証エラー メッセージのテキストを指定します。 このエラー メッセージは、Html.ValidationSummary() および Html.ValidationMessage() ヘルパー メソッドによってビューの中に表示されます。

検証規則が実行されると、ModelState の IsValid プロパティがチェックされます。 IsValid プロパティは、何か検証エラー メッセージが ModelState に追加された場合に false を返します。 検証が失敗した場合、Create フォームはエラー メッセージと共に再表示されます。

Note

正規表現リポジトリ http://regexlib.com から電話番号とメールアドレスを検証するための正規表現を取得しました

Edit アクションへの検証ロジックの追加

Edit() アクションは連絡先を更新します。 Edit() アクションは、Create() アクションとまったく同じ検証を実行する必要があります。 同じ検証コードを複製するのではなく、Create() アクションと Edit() アクションの両方が同じ検証メソッドを呼び出すように、Contact コントローラーをリファクターする必要があります。

変更された Contact コントローラー クラスはリスト 3 の中に含まれています。 このクラスには、Create() および Edit() アクションの両方の中で呼び出される新しい ValidateContact() メソッドがあります。

リスト 3 - Controllers\ContactController.cs

using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Controllers
{
    public class ContactController : Controller
    {
        private ContactManagerDBEntities _entities = new ContactManagerDBEntities();

        protected void ValidateContact(Contact contactToValidate)
        {
            if (contactToValidate.FirstName.Trim().Length == 0)
                ModelState.AddModelError("FirstName", "First name is required.");
            if (contactToValidate.LastName.Trim().Length == 0)
                ModelState.AddModelError("LastName", "Last name is required.");
            if (contactToValidate.Phone.Length > 0 && !Regex.IsMatch(contactToValidate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
                ModelState.AddModelError("Phone", "Invalid phone number.");
            if (contactToValidate.Email.Length > 0 && !Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
                ModelState.AddModelError("Email", "Invalid email address.");
        }

        public ActionResult Index()
        {
            return View(_entities.ContactSet.ToList());
        }

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

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
        {
            // Validation logic
            ValidateContact(contactToCreate);
            if (!ModelState.IsValid)
                return View();

            // Database logic
            try
            {
                _entities.AddToContactSet(contactToCreate);
                _entities.SaveChanges();
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        public ActionResult Edit(int id)
        {
            var contactToEdit = (from c in _entities.ContactSet
                                   where c.Id == id
                                   select c).FirstOrDefault();

            return View(contactToEdit);
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(Contact contactToEdit)
        {
            ValidateContact(contactToEdit);
            if (!ModelState.IsValid)
                return View();

            try
            {
                var originalContact = (from c in _entities.ContactSet
                                     where c.Id == contactToEdit.Id
                                     select c).FirstOrDefault();
                _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);
                _entities.SaveChanges();
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        public ActionResult Delete(int id)
        {
            var contactToDelete = (from c in _entities.ContactSet
                                 where c.Id == id
                                 select c).FirstOrDefault();

            return View(contactToDelete);
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Delete(Contact contactToDelete)
        {
            try
            {
                var originalContact = (from c in _entities.ContactSet
                                       where c.Id == contactToDelete.Id
                                       select c).FirstOrDefault();

                _entities.DeleteObject(originalContact);
                _entities.SaveChanges();
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

    }
}

まとめ

このイテレーションでは、Contact Manager アプリケーションに基本的なフォームの検証を追加しました。 この検証ロジックにより、ユーザーは FirstName および LastName プロパティの値を指定せずに、新しい連絡先の送信および既存の連絡先の編集ができなくなります。 さらに、ユーザーは有効な電話番号とメール アドレスを指定する必要があります。

このイテレーションでは、可能な限り簡単な方法で Contact Manager アプリケーションに検証ロジックを追加しました。 ただし、検証ロジックとコントローラー ロジックを混在させると、長期的には問題が発生します。 このアプリケーションは、時間の経過と共に維持および変更することがより困難になります。

次のイテレーションでは、検証ロジックとデータベース アクセス ロジックをコントローラーからリファクターします。 いくつかのソフトウェア設計原則を活用して、より疎結合でより維持しやすいアプリケーションを作成できるようにします。