網頁

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

 

以上

沒有留言:

張貼留言