トップ回答者
ASP.NET MVCで、Ajax.Beginformを使用して日時を設定する方法と、コントローラー上でModelState.IsValidをfalseにさせない方法

質問
-
ASP.NET MVC5でAjax.BeginFormでの日時の設定方法
javascriptのjQueryプラグインのGridを使用して、行を選択すると、フォーム上に反映し、新規登録、更新、削除をするボタンを置いて、EntityFrameworkでデータ操作をしようとしてます。
作成日時、更新日時は EntityFrameworkのDbContextの派生クラスでSaveChangesメソッドをオーバーライドして更新する予定なので、
フォーム上では無駄な処理かもしれませんが、
もし、作成日時、更新日時以外で日時を扱う場合があるとき、その方法がわからなかったことと、
今回、コントローラーに日時がうまくわたせなかった時に、ModelState.IsValidがfalseになってしまうので、trueになるようにしたいです。
日時をフォームに渡すときに、moment(row.getData().CreatedAt).ToDate() にした場合、コントローラーは実行できますが、ChromeのコンソールでUncaught TypeErrorが発生してました。
コントローラー側のモデルのCreateAt, UpdatedAtの日時は、0001/01/01 0:00:00となってました。
どなたかわかるかたいらっしゃいましたらご教授お願いします。
/Members/index.cshtml
@model Test.Models.Member @{ ViewBag.Title = "Member"; } <h2>@ViewBag.Title</h2> <div id="example-table"></div> @using (Ajax.BeginForm("Action", "Members", new AjaxOptions { HttpMethod = "POST", OnSuccess = "onSuccess", OnFailure = "onFailure" })) { @Html.AntiForgeryToken() <div class="form-horizontal"> @Html.ValidationSummary(true) <div class="form-group"> @Html.LabelFor(model => model.Id, new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Id, new { @id = "Id" }) @Html.ValidationMessageFor(model => model.Id) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Name, new { @id = "Name" }) @Html.ValidationMessageFor(model => model.Name) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Deleting, new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Deleting, new { @id = "Deleting" }) @Html.ValidationMessageFor(model => model.Deleting) </div> </div> <div class="form-group"> <div class="col-md-10"> @Html.HiddenFor(model => model.CreatedAt, new { @id = "CreatedAt" }) @Html.HiddenFor(model => model.UpdatedAt, new { @id = "UpdatedAt" }) @Html.HiddenFor(model => model.RowVersion, new { @id = "RowVersion" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" name="Create" value="新規登録" class="btn btn-default" /> <input type="submit" name="Edit" value="更新" class="btn btn-default" /> <input type="submit" name="Delete" value="削除" class="btn btn-default" /> </div> </div> </div> } @section Scripts { @Scripts.Render("~/bundles/jqueryval") <script> var table = new Tabulator("#example-table", { height: "311px", movableRows: true, ajaxURL: "@Url.Action("Gets", "Members")", ajaxProgressiveLoadDelay: 100, history: true, columns: [ { rowHandle: true, formatter: "handle", headerSort: false, frozen: true, width: 30, minWidth: 30 }, { title: "@Html.DisplayNameFor(model => model.Id)", field: "Id", frozen: true }, { title: "@Html.DisplayNameFor(model => model.Name)", field: "Name", sortable: true }, { title: "@Html.DisplayNameFor(model => model.Deleting)", field: "Deleting", formatter: "tick" }, { title: "@Html.DisplayNameFor(model => model.CreatedAt)", field: "CreatedAt", formatter: function (cell, formatterParams, onRendered) { return moment(cell.getValue()).format("YYYY-MM-DD HH:mm:ss.SSS"); } }, { title: "@Html.DisplayNameFor(model => model.CreatedAt)", field: "UpdatedAt", formatter: function (cell, formatterParams, onRendered) { return moment(cell.getValue()).format("YYYY-MM-DD HH:mm:ss.SSS"); } }, { title: "@Html.DisplayNameFor(model => model.RowVersion)", field: "RowVersion", visible: false }, ], rowSelectionChanged: function (data, rows) { console.log("selection change"); }, rowMoved: function (row) { console.log("Row has been moved"); }, rowClick: function (e, row) { $("#Id").val(row.getData().Id); $("#Name").val(row.getData().Name); $("#Deleting").val(row.getData().Deleting); // moment(row.getData().CreatedAt).ToDate() にするとコントローラーは実行できるけど // ChromeのコンソールでUncaught TypeErrorが発生 $("#CreatedAt").val(moment(row.getData().CreatedAt).ToDate()); $("#UpdatedAt").val(moment(row.getData().UpdatedAt).ToDate()); $("#RowVersion").val(row.getData().RowVersion); // 日時のテスト出力 console.log("DateTime1: " + row.getData().CreatedAt); console.log("DateTime2: " + moment(row.getData().CreatedAt)); console.log("DateTime3: " + moment(row.getData().CreatedAt).format("YYYY-MM-DD HH:mm:ss")); console.log("DateTime4: " + moment(row.getData().CreatedAt).ToDate()); console.log("DateTime5: " + JSON.stringify(row.getData().CreatedAt)); } }); function onSuccess(json) { console.log("success" + "\n"); console.log(json); }; function onFailure(json) { console.log("failure" + "\n"); //console.log(json); }; </script> }
Member.cs
using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Test.Models { [Table("M_Members")] public class Member { [DisplayName("ID")] [StringLength(20)] public string Id { get; set; } [DisplayName("氏名")] [StringLength(20)] public string Name { get; set; } [DisplayName("非表示")] public bool Deleting { get; set; } [DisplayName("作成日時")] public DateTime CreatedAt { get; set; } [DisplayName("更新日時")] public DateTime UpdatedAt { get; set; } [Timestamp] public byte[] RowVersion { get; set; } } }
MembersController.cs
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Net; using System.Web; using System.Web.Mvc; using Test.Models; namespace Test.Controllers { public class MembersController : Controller { private MvcBasicContext db = new MvcBasicContext(); // GET: /Members/ public ActionResult Index() { return View(); } public ActionResult Gets() { return Json(db.M_Members, JsonRequestBehavior.AllowGet); } // POST: /Members/Create // 過多ポスティング攻撃を防止するには、バインド先とする特定のプロパティを有効にしてください。 // 詳細については、http://go.microsoft.com/fwlink/?LinkId=317598 を参照してください。 [HttpPost] [HttpParamAction] [ValidateAntiForgeryToken] public ActionResult Create([Bind(Include = "Id,Name,Deleting,CreatedAt,UpdatedAt,RowVersion")] Member member) { if (ModelState.IsValid) {
// 新規登録なのでmodelの存在チェックが必要
db.M_Members.Add(member); db.SaveChanges(); return Json(db.M_Members, JsonRequestBehavior.AllowGet); } Response.StatusCode = (int)HttpStatusCode.BadRequest; return Json(new { ErrorMessage = "Error message1" }); } // POST: /Members/Edit/5 // 過多ポスティング攻撃を防止するには、バインド先とする特定のプロパティを有効にしてください。 // 詳細については、http://go.microsoft.com/fwlink/?LinkId=317598 を参照してください。 [HttpPost] [HttpParamAction] [ValidateAntiForgeryToken] public ActionResult Edit([Bind(Include = "Id,Name,Deleting,CreatedAt,UpdatedAt,RowVersion")] Member member) { if (ModelState.IsValid) { db.Entry(member).State = EntityState.Modified; // 新しくmodelをnewして準備する必要があるかも
// try ~ catchと、RowVersionのチェックが必要db.SaveChanges(); return Json(db.M_Members, JsonRequestBehavior.AllowGet); } Response.StatusCode = (int)HttpStatusCode.BadRequest; return Json(new { ErrorMessage = "Error message2" }); } // POST: /Members/Delete/5 [HttpPost] [HttpParamAction] [ValidateAntiForgeryToken] public ActionResult Delete([Bind(Include = "Id,Name,Deleting,CreatedAt,UpdatedAt,RowVersion")] Member member) { // 通常、物理削除はせず、削除フラグをtrue /* Member r = db.M_Members.Find(member.Id); db.M_Members.Remove(r); db.SaveChanges(); */ return Json(db.M_Members, JsonRequestBehavior.AllowGet); } protected override void Dispose(bool disposing) { if (disposing) { db.Dispose(); } base.Dispose(disposing); } } }
- 編集済み yuchan01 2019年7月21日 3:11
回答
-
質問者さんが参考にされたスレッドの話は今回の問題とは関係ないです。そのスレッドの話は TextBoxFor も EditorFor, HiddenFor と同じです。
問題の原因は、ブラウザから POST される HiddenFor (html の input type="hidden") の値です。デバッガではそれはわかりませんので、Fiddler などのキャプチャツールを使って調べてください。
原因を明確にしてきちんと対処できるようにしないと、また同じ問題で苦しむことになりますよ。
#Grid とかを使ってクライアントスクリプトで HiddenFor に送信する値を書き込もうとしているのですよね? 想像ですが、そこに問題がありそうな気がします。
- 回答としてマーク yuchan01 2019年7月27日 11:43
-
すぐにご返信できなくすみません、
ご指摘ありがとうございます。
HiddenForでは DateTimeをモデルにバインドできませんでしたので、
TextBoxForでdisplay: noneの方法に変えたところ、コントローラーが実行でき、モデルにバインドできました。
@Html.HiddenFor(model => model.CreatedAt, new { @id = "CreatedAt" })
↓
@Html.TextBoxFor(model => model.CreatedAt, new { @id = "CreatedAt", style = "display : none" })
私のケースでは、HiddenForの時に、2000-01-01 01:01:01 のように日時を手打ちして、ボタンを実行しても動きませんでした。
IsValidはtrueになり、Create(新規登録)のコントローラーは実行でき、DBが更新されましたが、
モデルにRowVersion(byte[]) プロパティを持っているため、
Edit(更新)の時に、RowVersionのデータをコントローラーへ、どのように渡せばよいかわからなく困ってます。
英語サイトなども調べてはいるのですが...
[Timestamp]
public byte[] RowVersion { get; set; }
最初の質問には含まれていない話になってしまいますが、
もしご存じであればご教授願えませんでしょうか?
すべての返信
-
コードについては参考程度の情報です、長いコードですみません。
Gridが含んでいてわかりにくくすみませんが、Gridについては詳細を理解して頂かなくて結構です。
要点をまとめさせていただきます。
index.cshtmlのみ参考としていただければ。知りたいことはAjax.BeginFormでの日時の取り扱いです。
・Gridに入れる日時の列は、以下のようにすると、 コントローラーからのJSON結果のGridへの表示が
yyyy-MM-DD HH:mm:ss.SSS 形式になる、指定しなかった場合は、/Date(1562688061013)/ のようになる
return moment(cell.getValue()).format("YYYY-MM-DD HH:mm:ss.SSS");
・Ajax.BeginFormのフォームに、Gridからの日時は渡せるが、コントローラー上のモデルのId、Name、Deletingは渡されているが、CreateAt, UpdatedAtの日時は、0001/01/01 0:00:00 となっていました。
作成日時、更新日時は SaveChangesで処理するので、通常はフォームでの入力値としては不要ですが、フォームで日時を扱う場合の取り扱い方法が知りたいです。
$("#CreatedAt").val(moment(row.getData().CreatedAt).ToDate());
・上記の様にGrid行の日時をフォームに渡すときに、("#CreatedAt").val(moment(row.getData().CreatedAt).ToDate()); にした場合、コントローラーは実行できますが、ChromeのコンソールでUncaught TypeErrorが発生してました。
・日時を正しく渡すことで、コントローラー上でModelState.isValidをtrueにしたい
- 編集済み yuchan01 2019年7月21日 5:05
-
質問者さんが無反応になってしまいましたが・・・
上のレスで、
> ありそうなのは、隠しフィールド (ですよね?) の文字列が DateTime 型にパースできない形式になっており、それがサーバーに送信されて、サーバーでパースできないから IsValid が false になった。結果、model に渡された日付が 0001/01/01 0:00:00 となったということです。
と書きましたが、それをちょっとだけ検証してみました。
yyyy-MM-DD HH:mm:ss.SSS 形式であればパースできるので問題はないですが、/Date(1562688061013)/ はパースできないので上に述べた結果になります。他に xxxxx とかの文字列ももちろんダメで、上に述べた結果になります。さらに、空白もダメです。
上記は全て以下の結果、即ち model には初期化されてない DateTime 型のオブジェクト(即ち、0001/01/01 0:00:00 という値の DateTime 型オブジェクト)が渡され、IsVaild はもちろん false になります。
という訳で、上のレスで書いた想像は当たっていると思います。
質問者さんのケースでは、隠しフィールドの値が /Date(1562688061013)/ とか空白になっていて、それがアクションメソッドに送信されているのではないかと思われます。 -
すぐにご返信できなくすみません、
ご指摘ありがとうございます。
HiddenForでは DateTimeをモデルにバインドできませんでしたので、
TextBoxForでdisplay: noneの方法に変えたところ、コントローラーが実行でき、モデルにバインドできました。
@Html.HiddenFor(model => model.CreatedAt, new { @id = "CreatedAt" })
↓
@Html.TextBoxFor(model => model.CreatedAt, new { @id = "CreatedAt", style = "display : none" })
私のケースでは、HiddenForの時に、2000-01-01 01:01:01 のように日時を手打ちして、ボタンを実行しても動きませんでした。
IsValidはtrueになり、Create(新規登録)のコントローラーは実行でき、DBが更新されましたが、
モデルにRowVersion(byte[]) プロパティを持っているため、
Edit(更新)の時に、RowVersionのデータをコントローラーへ、どのように渡せばよいかわからなく困ってます。
英語サイトなども調べてはいるのですが...
[Timestamp]
public byte[] RowVersion { get; set; }
最初の質問には含まれていない話になってしまいますが、
もしご存じであればご教授願えませんでしょうか?
-
HiddenFor、Editor、TextBoxで生成されるタグの違いはありますが、
フォーラムの過去の記事のSurferOnWwwさんのコメントを確認したところ、> asp.net mvc 3 の Html.DisplayFor と Html.HiddenFor で値が異なってしまう現象
HiddenForとEditorForのPOSTの時、には、modelの値ではなく、ポストされた値が表示されるとのことです。
日時のデータでうまくいかなかったのはこのことが関係していたと思います。
TextBoxForについては確証ありませんが、DisplayForと同じく、
modelの値が使われるので、日時のデータをモデルにバインドできたと思われます※確証があるわけではありませんので、間違っていましたらご指摘下さい。
timestampの話は別途スレッドを立てさせていただきます。
ありがとうございます。
- 編集済み yuchan01 2019年7月22日 16:30
-
質問者さんが参考にされたスレッドの話は今回の問題とは関係ないです。そのスレッドの話は TextBoxFor も EditorFor, HiddenFor と同じです。
問題の原因は、ブラウザから POST される HiddenFor (html の input type="hidden") の値です。デバッガではそれはわかりませんので、Fiddler などのキャプチャツールを使って調べてください。
原因を明確にしてきちんと対処できるようにしないと、また同じ問題で苦しむことになりますよ。
#Grid とかを使ってクライアントスクリプトで HiddenFor に送信する値を書き込もうとしているのですよね? 想像ですが、そこに問題がありそうな気がします。
- 回答としてマーク yuchan01 2019年7月27日 11:43