2013年2月18日 星期一

DropDownList DataAnnotation 於 MVC 3, MVC 4 的差異狀況

這篇是記錄一下在之前某一次 twMVC 週四固定聚會時有一位朋友所提出問題的解決方式,這一個問題其實不難,只因為是 MVC 3, MVC 4 兩個版本不同而造成的差異狀況,最根本的解決方式就是改用 MVC 4 來做網站,而如果無法升級為 MVC 4 的話,那就使用我的土砲方式也是可以的。

狀況:一個簡單的類別,各屬性都有標註 DataAnnotation Validation 標籤,其中一個屬性在前端會以下拉選單方式呈現,所以當前端的下拉選單沒有選值時就會顯示驗證訊息:如果把這個簡單類別包裝在一個 ViewModel 類別中,那麼在 MVC 3 專案中,這個下拉選單的驗證就會失效,而 MVC 4 專案則是完全正常。

在 ASP.NET MVC 3 環境下:

image

在 ASP.NET MVC 4 環境下:

image

接下來就來看看這是怎麼一回事。


首先來看看這個簡單類別的內容,

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>

那麼執行後的頁面與結果是正常無誤,

image

而當把 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>

但這樣的執行結果就如一開始所說的,下拉選單的驗證就失效了,

image

而同樣的情況改用 ASP.NET MVC 4 的環境卻是可以的,由此可以得知這個問題在 MVC 4 已經得到解決,這個等一下再來討論,先看看 MVC 3 環境下兩種狀況所產生的 HTML 原始碼為何,

下面這個是頁面直接使用 SomeThing 作為頁面 Model 所產生的下拉選單以及驗證訊息內容,

image

至於下面這個則是使用 SomeThingViewModel 作為頁面 Model 所產生的下拉選單內容,可以明顯的看出並沒有帶出驗證訊息內容,

image

 

而如果是 ASP.NET MVC 4 的環境呢?

使用 SomeThing 作為頁面 Model,

image

使用 SomeThingViewModel 作為頁面 Model,

image

在 ASP.NET MVC 4 則是不會受到影響,兩種情況都可以正確帶出標註在類別屬性上的 Validation Attribute 訊息。

 

土砲解決方法

找遍了很多文章與資源都沒有一個比較根本解決的方法,除了改用 ASP.NET MVC 4 來開發之外,都沒有比較好的選擇,所以我當時就用了很土砲的方式來解決,那就是直接在 View 裡面的 DropDownList Html Helper 做修改,在 htmlAttributes 裡面加入缺少了兩個屬性:data-val-requireddata-val

image

在 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>

這樣修改後就可以在前端頁面上帶出驗證訊息,

image

產生的 HTML Code,

image

 

 

查看 ASP.NET MVC 3 與 ASP.NET MVC 4 原始碼內容

為什麼會有這樣的結果呢?所以我就查看了 ASP.NET MVC 3 中 DropDownList 的原始碼,在 SelectExtensions.cs 的 SelectInternal Method 內,最後有個地方就是帶出 Validation Attributes 內容的程式,

image

再進入到 HtmlHelper.cs 的 GetUnobtrusiveValidationAttributes Method,這邊就不全部列出程式碼內容,有興趣了解的朋友可以自行查看 ASP. NET MVC 3 的原始碼內容,

image

 

接著再來查看 ASP.NET MVC 4 的原始碼內容,在 SelectExtensions.cs 的 SelectInternal Method 方法的最後也有帶出 Validation Attbibutes 內容的程式,

image

在 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的原始碼進行除錯」,仔細追查看看這兩個版本的程式內容。

 

以上

沒有留言:

張貼留言

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。