Question
MMTRS on Fri, 26 Sep 2014 11:00:23
Visual Studio Express 2013 for Web で MVC4 Web アプリケーション開発の勉強中です。
言語は VB です。
MVC3 のものですが、下記のサイトを見ながら勉強していますが、クライアント検証がうまくいかず行き詰っています。
http://www.atmarkit.co.jp/fdotnet/aspnetmvc3/index/
状況としては、上記サイトで紹介されているサンプルで、Books/Create の「出版社」の項目に、本来エラーとなるはずの内容を入力しても、クライアント側の検証が機能していないのか、エラーメッセージが表示されず、そのままサーバー側に送信すると、再度 Books/Create の画面が表示されて「出版社」の項目にカーソルが移るのでエラーの扱いにはなっているようなのですが、こちらもエラーメッセージが表示されません。
デバッグ実行をしてみると、項目への入力時に独自検証ルールの function で false が返されてはいるようです。
MVC3 と MVC4 では上記に関係するところで何か変更が必要でしょうか。
もしくは上記のサイトに書かれていない部分で注意点などありましたら、教えていただけると嬉しいです。
ソースコードについては何度も見直しましたので、jQuery のバージョンに関する記述等の一部を除いては、上記サイトを順に進めていったものと同じになっていると思いますし、どこを提示したら良いかがわからないというのもあり、必要に応じて追って提示させていただければと思います。
最低限の条件として、Web.config に以下は記述してあります。
<add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" />
よろしくお願いします。
Replies
SurferOnWww on Fri, 26 Sep 2014 12:00:37
具体的にどうやったのか分かりませんが(参考にされてるページを隅から隅までよく読めば分かると言わないでくださいね)、MVC4 のインターネットアプリケーションのテンプレートで作ったアプリなら必要な設定はすでにされているので、難しいことは何もないはずなんですが。
ASP.NET がレンダリングする html 要素に、クライアントサイドでの jQuery ライブラリによる検証に必要な属性(例: data-val="true" など)は追加されているでしょうか? まずそこを確認してください。
@IT の記事を読んで議論するは回答者にとって負担が大きいです。以下のページの簡単なサンプルのような短いコードで、コピペすれば動くもので話を進めませんか?
MMTRS on Fri, 26 Sep 2014 12:43:28
SurferOnWwwさん、ありがとうございます。
>ASP.NET がレンダリングする html 要素に、クライアントサイドでの jQuery ライブラリによる検証に必要な属性(例: data-val="true" など)は追加されているでしょうか? まずそこを確認してください。
以下の部分でしょうか。
<div class="editor-label"> <label for="Publish">出版社</label> </div> <div class="editor-field"> <input class="text-box single-line" data-val="true" data-val-inarray="出版社は翔泳社,技術評論社,秀和システム,毎日コミュニケーションズ,日経BP社,インプレスジャパンのいずれかで指定して下さい。" data-val-inarray-opts="翔泳社,技術評論社,秀和システム,毎日コミュニケーションズ,日経BP社,インプレスジャパン" data-val-length="出版社は30文字以内で入力して下さい。" data-val-length-max="30" id="Publish" name="Publish" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="Publish" data-valmsg-replace="true"></span> </div>
data-val-inarray と data-val-inarray-opts がそれにあたるかと思います。
>@IT の記事を読んで議論するは回答者にとって負担が大きいです。以下のページの簡単なサンプルのような短いコードで、コピペすれば動くもので話を進めませんか?
そうですね、大変失礼しました。
以下に実際のコードを記載します。
もし足りないものがありましたら再度お願いします。
●Model(Books.vb)
Imports System.ComponentModel Imports System.ComponentModel.DataAnnotations Public Class Book <Key()> <DisplayName("ISBNコード")> <Required(ErrorMessage:="{0}は必須です。")> <RegularExpression("[0-9]{3}-[0-9]{1}-[0-9]{3,5}-[0-9]{3,5}-[0-9A-Z]{1}", ErrorMessage:="{0}はISBNの形式で入力して下さい。")> Public Property Isbn() As String 'ISBNコード <DisplayName("書名")> <Required(ErrorMessage:="{0}は必須です。")> <StringLength(100, ErrorMessage:="{0}は{1}文字以内で入力して下さい。")> Public Property Title() As String '書名 <DisplayName("価格")> <Range(100, 100000, ErrorMessage:="{0}は{1}~{2}の間で入力して下さい。")> Public Property Price() As Integer? '価格 <DisplayName("出版社")> <InArray("翔泳社,技術評論社,秀和システム,毎日コミュニケーションズ,日経BP社,インプレスジャパン")> <StringLength(30, ErrorMessage:="{0}は{1}文字以内で入力して下さい。")> Public Property Publish() As String '出版社 <DisplayName("刊行日")> <Required(ErrorMessage:="{0}は必須です。")> Public Property Published() As Date '刊行日 Public Overridable Property Reviews() As ICollection(Of Review) 'レビュー End Class
●Controller(BooksController.vb)
Imports System.Data.Entity Public Class BooksController Inherits System.Web.Mvc.Controller Private db As New MyMvcContext ' ' GET: /Books/ Function Index() As ActionResult Return View(db.Books.ToList()) End Function ' ' GET: /Books/Details/5 Function Details(Optional ByVal id As String = Nothing) As ActionResult Dim book As Book = db.Books.Find(id) If IsNothing(book) Then Return HttpNotFound() End If Return View(book) End Function ' ' GET: /Books/Create Function Create() As ActionResult Return View() End Function ' ' POST: /Books/Create <HttpPost()> _ <ValidateAntiForgeryToken()> _ Function Create(ByVal book As Book) As ActionResult If ModelState.IsValid Then db.Books.Add(book) db.SaveChanges() Return RedirectToAction("Index") End If Return View(book) End Function ' ' GET: /Books/Edit/5 Function Edit(Optional ByVal id As String = Nothing) As ActionResult Dim book As Book = db.Books.Find(id) If IsNothing(book) Then Return HttpNotFound() End If Return View(book) End Function ' ' POST: /Books/Edit/5 <HttpPost()> _ <ValidateAntiForgeryToken()> _ Function Edit(ByVal book As Book) As ActionResult If ModelState.IsValid Then db.Entry(book).State = EntityState.Modified db.SaveChanges() Return RedirectToAction("Index") End If Return View(book) End Function ' ' GET: /Books/Delete/5 Function Delete(Optional ByVal id As String = Nothing) As ActionResult Dim book As Book = db.Books.Find(id) If IsNothing(book) Then Return HttpNotFound() End If Return View(book) End Function ' ' POST: /Books/Delete/5 <HttpPost()> _ <ActionName("Delete")> _ <ValidateAntiForgeryToken()> _ Function DeleteConfirmed(ByVal id As String) As RedirectToRouteResult Dim book As Book = db.Books.Find(id) db.Books.Remove(book) db.SaveChanges() Return RedirectToAction("Index") End Function Protected Overrides Sub Dispose(ByVal disposing As Boolean) db.Dispose() MyBase.Dispose(disposing) End Sub End Class
●View(Books/Create.vbhtml)
@ModelType MvcWebApp.Book @Code ViewData("Title") = "Create" End Code <h2>Create</h2> <script src="@Url.Content("~/Scripts/jquery-1.8.2.min.js")" type="text/javascript"></script> <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> <script src="@Url.Content("~/Scripts/InArray.js")" type="text/javascript"></script> @Using Html.BeginForm() @Html.AntiForgeryToken() @Html.ValidationSummary(True) @<fieldset> <legend>Book</legend> <div class="editor-label"> @Html.LabelFor(Function(model) model.Isbn) </div> <div class="editor-field"> @Html.EditorFor(Function(model) model.Isbn) @Html.ValidationMessageFor(Function(model) model.Isbn) </div> <div class="editor-label"> @Html.LabelFor(Function(model) model.Title) </div> <div class="editor-field"> @Html.EditorFor(Function(model) model.Title) @Html.ValidationMessageFor(Function(model) model.Title) </div> <div class="editor-label"> @Html.LabelFor(Function(model) model.Price) </div> <div class="editor-field"> @Html.EditorFor(Function(model) model.Price) @Html.ValidationMessageFor(Function(model) model.Price) </div> <div class="editor-label"> @Html.LabelFor(Function(model) model.Publish) </div> <div class="editor-field"> @Html.EditorFor(Function(model) model.Publish) @Html.ValidationMessageFor(Function(model) model.Publish) </div> <div class="editor-label"> @Html.LabelFor(Function(model) model.Published) </div> <div class="editor-field"> @Html.EditorFor(Function(model) model.Published) @Html.ValidationMessageFor(Function(model) model.Published) </div> <p> <input type="submit" value="Create" /> </p> </fieldset> End Using <div> @Html.ActionLink("Back to List", "Index") </div> @Section Scripts @Scripts.Render("~/bundles/jqueryval") End Section
●検証属性定義(InArrayAttribute.vb)
Imports System.ComponentModel.DataAnnotations Imports System.Globalization <AttributeUsage(AttributeTargets.Property, AllowMultiple:=False)> Public Class InArrayAttribute Inherits ValidationAttribute Implements IClientValidatable Private _opts As String Public Sub New(ByVal opts As String) _opts = opts ErrorMessage = "{0}は{1}のいずれかで指定して下さい。" End Sub Public Overrides Function FormatErrorMessage(name As String) As String Return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, _opts) End Function Public Overrides Function IsValid(value As Object) As Boolean If value Is Nothing Then Return True If Array.IndexOf(_opts.Split(","c), value) = -1 Then Return False End If Return True End Function '' クライアントに送信する検証情報の生成 Public Function GetClientValidationRules(ByVal metadata As ModelMetadata, ByVal context As ControllerContext) As IEnumerable(Of ModelClientValidationRule) Implements IClientValidatable.GetClientValidationRules '' 検証ルールを準備(検証名、エラーメッセージ) Dim rule As New ModelClientValidationRule() With { _ .ValidationType = "inarray", _ .ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()) _ } rule.ValidationParameters("opts") = _opts '検証パラメータ Dim list As New List(Of ModelClientValidationRule)() list.Add(rule) Return list End Function End Class
●検証JavaScript(InArray.js)
// インテリセンス機能を有効化 /// <reference path="jquery-1.8.2.intellisense.js" /> /// <reference path="jquery.validate-vsdoc.js" /> /// <reference path="jquery.validate.unobtrusive.min.js" /> // inarray検証をjQuery Validationに登録 $.validator.addMethod('inarray', function (value, element, param) { // 入力値が空の場合は検証をスキップ value = $.trim(value); if (value === '') { return true; } // カンマ区切りテキストを分解し、入力値valueと比較 if ($.inArray(value, param.split(',')) === -1) { return false; } return true; } ); // inarray検証と、そのパラメータoptsを登録 $.validator.unobtrusive.adapters.addSingleVal('inarray', 'opts');
以上です。
長くなりましたが、よろしくお願いします。
SurferOnWww on Fri, 26 Sep 2014 14:22:12
> 以下に実際のコードを記載します。
> もし足りないものがありましたら再度お願いします。
それ、コピペするだけでは動かないですよ。SQL Server データベースと EDM が必要です。
Data Annotation 検証でクライアント側の検証が動くかどうか見るだけならもっと簡単なコードで済むでしょう。先に紹介したページのような。
SurferOnWww on Sat, 27 Sep 2014 01:21:30
【追伸】
アップされたコードは検証してませんが、ざっと見た限り View が間違っているような気がします。
MVC4 ではJavaScript/CSS ファイルの縮小化と統合処理の自動機能が追加されています。詳しくは下記ページ参照。
Bundling and Minification
http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification
それと、その機能のない MVC3 のコードがごっちゃになっているようです。
縮小化と統合処理は、上のページに書いてある通り、開発中(compilation debug="true")は無効になりますが、その場合でも外部スクリプトファイルの定義は Scripts.Render メソッドでレンダリングされます。
自分の環境 VS2010 MVC4 のインターネットテンプレートで作った Web アプリの場合、マスターページ (_Layout.cshtml) で @Scripts.Render("~/bundles/jquery") は定義され、ウィザードで自動生成するビュー Create.cshtml に @Scripts.Render("~/bundles/jqueryval") が定義されます。
その結果、以下の外部スクリプトファイルの定義がレンダリングされます。
<script src="/Scripts/jquery-1.7.1.js"></script> <script src="/Scripts/jquery.unobtrusive-ajax.js"></script> <script src="/Scripts/jquery.validate.js"></script> <script src="/Scripts/jquery.validate.unobtrusive.js"></script>
なので、自力でビューに Url.Content を書いて定義する必要はないはずです。
まずはそのあたりに注意して修正してみてください。
SurferOnWww on Mon, 29 Sep 2014 08:08:36
質問者さんが出てこないので、このスレッドは未解決で放置になってしまいそうですが、それも何なので、自分が @IT の記事(MVC3 ベース)の検証関係のコードを、VS2010 MVC4 を使って確認した結果を書いておきます。
結論から言えば、自作検証属性を含め、JavaScript / jQuery によるクライアントサイドの検証はすべて期待通り動きました。
問題は、MVC4 のインターネットテンプレートでは JavaScript ファイルの自動バンドル処理が追加されているのに、それが使えてなかったからです。
マスターページ (_Layout.cshtml)
MVC4 のインターネットテンプレートで自動生成されるマスターページは以下のようになっています。
<!DOCTYPE html>
・・・中略・・・
@Scripts.Render("~/bundles/jquery") @RenderSection("scripts", required: false) </body> </html>
@Scripts.Render("~/bundles/jquery") の位置に jquery.js の定義が、@RenderSection("scripts", required: false) の位置に検証用外部スクリプトファイルの定義が(ビューによって)レンダリングされます。
App_Start/BundleConfig.cs
自作検証用のスクリプトファイルをバンドルできるように以下のように定義を追加します。
namespace Mvc4App { public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { // 中略 // 下記を追加 bundles.Add(new ScriptBundle("~/bundles/inarray").Include( "~/Scripts/InArray.js")); // 中略 } } }
ビュー (Create.cshtml)
ビューには @Scripts.Render("~/bundles/jqueryval") というコードが自動生成されているはずです。それに "~/bundles/inarray" を追記します。以下のようになります。
@section Scripts { @Scripts.Render("~/bundles/jqueryval", "~/bundles/inarray") }
質問者さんがアップしたビューのコードにある外部スクリプトファイルの定義は一切不要です。
MMTRS on Mon, 29 Sep 2014 10:28:11
SurferOnWwwさん、ありがとうございます。
今すぐは確認できないので、後ほど確認して改めてご報告します。
MMTRS on Mon, 29 Sep 2014 12:19:00
無事にできました。大変助かりました。
ありがとうございます。