AjaxHelper を利用した作成・更新フォーム

ASP.NET MVC と jQuery で新規登録ダイアログと編集ダイアログ」で、サーバーから取得した HTML ドキュメント中のデータから(サーバーとの会話をせずに)更新などのダイアログを作成するコードを書いてみました。こんどは AjaxHelper を用いて、サーバーから更新などのフォームを取得して、部分的にページを書き換えてみたいと思います(最終的に、これもダイアログ表示までもっていきます)。

画面はこんな感じです。

一覧画面
作成フォームが追加されたページ
詳細画面が追加されたページ
修正フォームが追加されたページ

モデルはこんな感じです(TestModel.cs)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;

namespace TestAjaxPartialForm001.Models
{
    public class DataModel
    {
        public int Id { get; set; }

        [Required]
        [Display(Name = "名前")]
        public string Name { get; set; }

        [Required]
        [RegularExpression(@"^[\x01-\x7F]+@(([-a-z0-9]+\.)*[a-z]+|\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\])",
            ErrorMessage = "正しいメールアドレスを入力してください。")]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "メールアドレス")]
        public string Email { get; set; }

        [Required]
        [DataType(DataType.PhoneNumber)]
        [Display(Name = "電話番号")]
        public string Phone { get; set; }
    }

    static public class TestModel
    {
        static private IList<DataModel> _dataModel;

        static TestModel()
        {
            _dataModel = new List<DataModel>
            {
                new DataModel { Id = 1, Name = "user1", Email = "user1@example.com", Phone = "0123-12-0123" },
                new DataModel { Id = 2, Name = "user2", Email = "user2@example.com", Phone = "0123-12-1234" },
                new DataModel { Id = 3, Name = "user3", Email = "user3@example.com", Phone = "0123-23-0123" },
                new DataModel { Id = 4, Name = "user4", Email = "user4@example.com", Phone = "0123-23-1234" }
            };
        }

        /// <summary>
        /// 連絡先リストから <Id, Name> のディクショナリを生成して返却する
        /// </summary>
        /// <returns></returns>
        static public IDictionary<int, string> GetAllName()
        {
            return _dataModel.ToDictionary(item => item.Id, item => item.Name);
        }

        static public DataModel GetContact(int id)
        {
            return _dataModel.Where(c => c.Id == id).FirstOrDefault();
        }

        static public void CreateContact(DataModel model)
        {
            if (_dataModel.Where(c => c.Email == model.Email).Count() != 0)
                throw new ValidationException("同一のメールアドレスが既に登録されているので追加できませんでした。",null, "Email");
            model.Id = _dataModel.Max(c => c.Id) + 1;
            _dataModel.Add(model);
        }

        static public void ModifyContact(DataModel model)
        {
            if (_dataModel.Where(c => c.Id != model.Id).Where(c => c.Email == model.Email).Count() != 0)
                throw new ValidationException("同一のメールアドレスが既に登録されているので変更できませんでした。", null, "Email");
            var contact = _dataModel.Where(c => c.Id == model.Id).FirstOrDefault();
            contact.Name = model.Name;
            contact.Email = model.Email;
            contact.Phone = model.Phone;
        }
    }
}

次はコントローラです(HomeController.cs)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.Web;
using System.Web.Mvc;
using TestAjaxPartialForm001.Models;

namespace TestAjaxPartialForm001.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Message = "Welcome to ASP.NET MVC!";

            return View();
        }

        public ActionResult ListContacts()
        {
            var list = TestModel.GetAllName();
            return View(list);
        }

        public ActionResult GetContact(int id)
        {
            if (Request.IsAjaxRequest())    // 非同期通信か?
            {
                var contact = TestModel.GetContact(id);
                return PartialView(contact);
            }
            else
                return new EmptyResult();
        }

        public ActionResult CreateContact()
        {
            if (Request.IsAjaxRequest())    // 非同期通信か?
                return PartialView();
            else
                return new EmptyResult();
        }

        [HttpPost]
        public ActionResult CreateContact(DataModel model)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    TestModel.CreateContact(model);
                }
                catch (ValidationException e)
                {
                    ModelState.AddModelError(e.Value.ToString(), e.Message);
                    return View();
                }

                return RedirectToAction("ListContacts");
            }

            return View();
        }

        public ActionResult EditContact(int id)
        {
            if (Request.IsAjaxRequest())    // 非同期通信か?
            {
                var contact = TestModel.GetContact(id);
                return PartialView(contact);
            }
            else
            {
                return new EmptyResult();
            }
        }

        [HttpPost]
        public ActionResult EditContact(DataModel model)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    TestModel.ModifyContact(model);
                }
                catch (ValidationException e)
                {
                    ModelState.AddModelError(e.Value.ToString(), e.Message);
                    return View();
                }

                return RedirectToAction("ListContacts");
            }

            return View();
        }

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

GetContact(int id), CreateContact(), EditContact(int id) の3つがページに追加するフォーム等を取得しに非同期通信でアクセスしてくるアクションになり、正当な要求であれば、PartialView を返します。

次にビューですが、まずは /Views/Shared/_Layout.cshtml から。

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
    @RenderSection("scripts", required:false)
</head>
<body>
    <div class="page">
        <div id="header">
            <div id="title">
                <h1>My MVC Application</h1>
            </div>
            <div id="logindisplay">
                @Html.Partial("_LogOnPartial")
            </div>
            <div id="menucontainer">
                <ul id="menu">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                </ul>
            </div>
        </div>
        <div id="main">
            @RenderBody()
        </div>
        <div id="footer">
        </div>
    </div>
</body>
</html>

次に /Views/Home/Index.cshtml

@{
    ViewBag.Title = "Home Page";
}

<h2>@ViewBag.Message</h2>
<p>
    @Html.ActionLink("名前の一覧を表示", "ListContacts")
</p>

次は、/Views/Home/ListContacts.cshtml

@model IDictionary<int, string>

@{
    ViewBag.Title = "ListContacts";
}

@section scripts {
<script src="@Url.Content("~/Scripts/MicrosoftAjax.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/MicrosoftMvcAjax.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
}

<h2>@ViewBag.Title</h2>

@Ajax.ActionLink("追加", "CreateContact", new AjaxOptions() { UpdateTargetId = "editForm" })

<table>
    <tr>
        <th>名前</th>
        <th></th>
    </tr>
@foreach (var item in Model)
{
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Value)
        </td>
        <td>
            @Ajax.ActionLink("詳細", "GetContact", new { id = @item.Key },
                new AjaxOptions() { UpdateTargetId = "editForm" }) |
            @Ajax.ActionLink("編集", "EditContact", new { id = @item.Key },
                new AjaxOptions() { UpdateTargetId = "editForm" })
        </td>
    </tr>
}
</table>

<div>
    @Html.ActionLink("戻る", "Index")
</div>

<div id="editForm" style="margin-top:1em">
</div>

Ajax.ActionLink()で非同期通信を行うリンクを記述します。

@Ajax.ActionLink(“追加”, “CreateContact”, new AjaxOptions() { UpdateTargetId = “editForm” }) の場合「追加」のリンクをクリックすると、サーバーへ非同期通信を行い、取得したフォームを <div id=”editForm”></div>のところへ挿入します。

注意点として、<script src=”@Url.Content(“~/Scripts/jquery.unobtrusive-ajax.min.js”)” type=”text/javascript”></script> の参照設定が無いと、非同期通信が行われません。

あとは、部分ビュー3つです。

/Views/Home/CreateContact.cshtml

@model TestAjaxPartialForm001.Models.DataModel

<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>追加画面</legend>

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

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

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

        <p>
            <input type="submit" value="追加して保存" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("追加画面を閉じる", "ListContacts")
</div>

/Views/Home/GetContact.cshtml

@model TestAjaxPartialForm001.Models.DataModel

<fieldset>
    <legend>詳細画面</legend>

    <div class="display-label">@Html.LabelFor(model => model.Name)</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Name)
    </div>

    <div class="display-label">@Html.LabelFor(model => model.Email)</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Email)
    </div>

    <div class="display-label">@Html.LabelFor(model => model.Phone)</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Phone)
    </div>
</fieldset>
<p>
    @Html.ActionLink("詳細画面を閉じる", "ListContacts")
</p>

/Views/Home/EditContact.cshtml

@model TestAjaxPartialForm001.Models.DataModel

<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>編集画面</legend>

        @Html.HiddenFor(model => model.Id)

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

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

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

        <p>
            <input type="submit" value="変更を保存" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("変更画面を閉じる", "ListContacts")
</div>

コードを見て分かるとおり、Javascript をまったく記述しないで非同期でのページ書き換えが出来ています。ただ、部分ビューですので、サーバー側での検証エラー(メールアドレスの重複を許さないなど)をクライアントへ通知しようとすると、

サーバー側での検証エラーの通知

このように、編集フォームのみの画面になってしまいます。

逆に言うと、サーバー側でしか把握できない検証エラーはないとか、検証エラーが発生したときの画面はこれで構わないのであれば、Javascript をまったく書かないで実現できますね。

なお、あくまで感触を掴むために書いたものなので、エラー・ハンドリングを省いています。もし参考にされる方がいらしたら、ちゃんとエラー処理をしてください 😉

何か気がついたこととかあったら、指摘していただけると嬉しいです 🙂

次回は、サーバー側で把握した検証エラーをデータ入力しているときと同じ状態の画面で通知できるようにします。


AjaxHelper を利用した作成・更新フォーム」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です