「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件のフィードバック