這篇是記錄一下在之前某一次 twMVC 週四固定聚會時有一位朋友所提出問題的解決方式,這一個問題其實不難,只因為是 MVC 3, MVC 4 兩個版本不同而造成的差異狀況,最根本的解決方式就是改用 MVC 4 來做網站,而如果無法升級為 MVC 4 的話,那就使用我的土砲方式也是可以的。
狀況:一個簡單的類別,各屬性都有標註 DataAnnotation Validation 標籤,其中一個屬性在前端會以下拉選單方式呈現,所以當前端的下拉選單沒有選值時就會顯示驗證訊息:如果把這個簡單類別包裝在一個 ViewModel 類別中,那麼在 MVC 3 專案中,這個下拉選單的驗證就會失效,而 MVC 4 專案則是完全正常。
在 ASP.NET MVC 3 環境下:
在 ASP.NET MVC 4 環境下:
接下來就來看看這是怎麼一回事。
首先來看看這個簡單類別的內容,
public class SomeThing{[Display(Name = "編號")]
[Required(ErrorMessage = "請輸入編號")]
public Guid ID
{get;
set;
}[Display(Name = "名稱")]
[Required(ErrorMessage = "請輸入名稱")]
[StringLength(100, MinimumLength = 2, ErrorMessage = "名稱字串長度為 10 ~ 100")]
public string Name{get;
set;
}[Display(Name = "類別")]
[Required(ErrorMessage = "請輸入類別")]
public string Category{get;
set;
}public SomeThing()
{this.ID = Guid.NewGuid();
}}
我們在 View 直接使用 SomeThing 這個類別作為頁面的 Model ,
@model Mvc3Version.Models.SomeThing@{ViewBag.Title = "TestMethod";}<h2>TestMethod</h2><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>SomeThing</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.Category)</div><div class="editor-field">@Html.DropDownListFor(model => model.Category, (IEnumerable<SelectListItem>)ViewBag.Categories, "please select a option...")@Html.ValidationMessageFor(model => model.Category)</div><p><input type="submit" value="Create" /></p></fieldset>}<div>@Html.ActionLink("Back to List", "Index")</div>
那麼執行後的頁面與結果是正常無誤,
而當把 SomeThing 這個類別給包裝在另一個 ViewModel 類別中,
public class SomeThingViewModel{public SomeThing foo
{get;
set;
}}
在 View 裡面的編排上是沒有什麼差異,祝要差別在於 View 頁面的 Model 改使用 SomeThingViewModel,
@model Mvc3Version.Models.SomeThingViewModel@{ViewBag.Title = "WrapViewModel";}<h2>WrapViewModel</h2><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>SomeThingViewModel</legend><div class="editor-label">@Html.LabelFor(model => model.foo.Name)</div><div class="editor-field">@Html.EditorFor(model => model.foo.Name)@Html.ValidationMessageFor(model => model.foo.Name)</div><div class="editor-label">@Html.LabelFor(model => model.foo.Category)</div><div class="editor-field">@Html.DropDownListFor(model => model.foo.Category, (IEnumerable<SelectListItem>)ViewBag.Categories, "please select a option...")@Html.ValidationMessageFor(model => model.foo.Category)</div><p><input type="submit" value="Create" /></p></fieldset>}<div>@Html.ActionLink("Back to List", "Index")</div>
但這樣的執行結果就如一開始所說的,下拉選單的驗證就失效了,
而同樣的情況改用 ASP.NET MVC 4 的環境卻是可以的,由此可以得知這個問題在 MVC 4 已經得到解決,這個等一下再來討論,先看看 MVC 3 環境下兩種狀況所產生的 HTML 原始碼為何,
下面這個是頁面直接使用 SomeThing 作為頁面 Model 所產生的下拉選單以及驗證訊息內容,
至於下面這個則是使用 SomeThingViewModel 作為頁面 Model 所產生的下拉選單內容,可以明顯的看出並沒有帶出驗證訊息內容,
而如果是 ASP.NET MVC 4 的環境呢?
使用 SomeThing 作為頁面 Model,
使用 SomeThingViewModel 作為頁面 Model,
在 ASP.NET MVC 4 則是不會受到影響,兩種情況都可以正確帶出標註在類別屬性上的 Validation Attribute 訊息。
土砲解決方法
找遍了很多文章與資源都沒有一個比較根本解決的方法,除了改用 ASP.NET MVC 4 來開發之外,都沒有比較好的選擇,所以我當時就用了很土砲的方式來解決,那就是直接在 View 裡面的 DropDownList Html Helper 做修改,在 htmlAttributes 裡面加入缺少了兩個屬性:data-val-required 與 data-val。
在 htmlAttributes 加入 data 屬性時,使用 underline 輸入,在前端 render Html 時就會轉為 dash line,
<div class="editor-label">@Html.LabelFor(model => model.foo.Category)</div><div class="editor-field">@Html.DropDownListFor(model => model.foo.Category, (IEnumerable<SelectListItem>)ViewBag.Categories, "please select a option...", new { data_val_required="請輸入類別", data_val="true" })@Html.ValidationMessageFor(model => model.foo.Category)</div>
這樣修改後就可以在前端頁面上帶出驗證訊息,
產生的 HTML Code,
查看 ASP.NET MVC 3 與 ASP.NET MVC 4 原始碼內容
為什麼會有這樣的結果呢?所以我就查看了 ASP.NET MVC 3 中 DropDownList 的原始碼,在 SelectExtensions.cs 的 SelectInternal Method 內,最後有個地方就是帶出 Validation Attributes 內容的程式,
再進入到 HtmlHelper.cs 的 GetUnobtrusiveValidationAttributes Method,這邊就不全部列出程式碼內容,有興趣了解的朋友可以自行查看 ASP. NET MVC 3 的原始碼內容,
接著再來查看 ASP.NET MVC 4 的原始碼內容,在 SelectExtensions.cs 的 SelectInternal Method 方法的最後也有帶出 Validation Attbibutes 內容的程式,
在 ASP.NET MVC 4 這邊就有把帶出 Validation Attributes 內容的程式碼給獨立成一個專門的類別「UnobtrusiveValidationAttributesGenerator.cs」,另外其他的 HtmlHelper.cs 等內容也有不同,其中比較大的差異在於,ASP.NET MVC 4 的 SelectInternal Method 有多了一個 ModelMetadata 的參數,所以在 ASP.NET MVC 4 所得到的結果就與 ASP.NET MVC 3 有所不同。
最後:
如果可以的話,就改使用 ASP.NET MVC 4 來開發專案,假如不行的話,那就用我的土砲方式來做解決。
另外如果有興趣追查兩個版本 ASP.NET MVC 原始碼在這個地方為何會有這樣差異的朋友,可以參考之前我所寫的「ASP.NET MVC 3 - 加入 ASP.NET MVC 3 原始碼來偵錯」與微軟 MVP - Sky Chang 的「ASP.NET MVC - 使用ASP.NET MVC 4的原始碼進行除錯」,仔細追查看看這兩個版本的程式內容。
以上
沒有留言:
張貼留言