在這一篇我們將來看一下在寫Mvc裡面最重要的一個概念,也就是強型別的View(Strong Type View)和ViewModel。
同步發表於我的部落格:http://alantsai2007.blogspot.tw/2014/09/BuildYourOwnApplicationFrameworkOnMvc-06-ViewModelIntro.html
Mvc裡面的View使用的是Razor語法,而每一個View能夠定義傳進來的Model形態是什麼。透過這種方式,在寫View的時候不止能夠在Compile time的時候知道對於傳進來的Model使用上面有沒有正確,配上HtmlHelper,還能夠建立能夠和Model binding成功的對應html input。
強型別的View好處多多,但是問題在於這個型別應該要用什麼?如果用預設的Mvc做Scaffolding的話,預設用的是Entity Framework裡面的DbSetEntity作為Model,就是實際Table 對應的Entity作為強型別View的Model。
使用Entity Framework的Entity作為ViewModel好還是不好有點看個人的習慣,不過我個人覺的是不好。
基本上有以下幾個問題:
我們先來看一下預設Scaffolding Create Post Action的片段:
預設Scaffolding出來的Create Post action片段
我們可以看到英文的註解,和[Bind(Include = "Id,DisplayName,Url")]看到,其實它是在限制那些Property應該要做Model Binding。因此,以這個例子,Id、 DisplayName和Url將會做ModelBinding。
假設今天我們不希望Id是由使用者輸入,而是系統產生,因此我們會把畫面上面產生輸入Id的HtmlHelper拿掉。假設,使用者是透過我們的form表單post資訊回來,這個不會有問題。
但是,如果有人在Form post之前,增加一個欄位並且測出有Id這個欄位,那麼他post過來帶上Id這個欄位的是時候,我們存到DB值就錯了。
這個問題在於,假設今天我要刪除某一個Property是由使用者輸入,會需要:
上面關於第一點,如果忘記還好發現,因為忘記刪掉,畫面會看到欄位。但是,第二個就不好發現了。因為只要不是惡意嘗試,更本不會注意到。
ViewModel就可以解決這個問題。同時,用ViewModel還符合OO裡面的封裝(Encapsulation)概念,只開放要使用的欄位。
這個意思是,假設我們要顯示的值是不同地方組合出來的,要用Entity做ViewModel顯然不適合,因為Entity是Entity Framework對應到DB Table,因此不能夠隨意變動裡面的Property。
我們用個簡單的例子,假設我需要顯示一個值是DisplayName和Url兩個欄位的值,如果使用是Entity做ViewModel,我們只能夠在View裡面手動把兩個 Property做concatenate顯示。如果這個View多處需要顯示這個值,或是不同的View都需要顯示這個值,用concatenate的方式做顯示有個問題,如果有天要替換兩個結合的Property,只能用搜索替換的方式來修改,很容易改錯,如果使用ViewModel就能夠解決這個問題。
只要在ViewModel定義一個Property,這個Property是兩個Property concatenate的結果,顯示在View的時候,就用那個Property。之後如果要改值,只需要改Property,所有的 View顯示的就會一起改變。
講了這們多,我們就看一下ViewModel到底是什麼。
其實ViewModel的定義有些細節上面會不同,因此我下面介紹的屬於我個人覺得比較適合ViewModel的定義。個人在使用上符合自己習慣即可。
首先,ViewModel就像它的名字一樣,用於顯示View的Model
而我這邊的ViewModel定義是:
我們可以用ViewModel來解決Model Binding的問題:
public class LinkCreateViewModel
{
public string DisplayName { get; set; }
public string Url { get; set; }
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(LinkCreateViewModel link)
{
if (ModelState.IsValid)
{
// 只設定需要的欄位
Link enity = new Link()
{
DisplayName = link.DisplayName,
Url = link.Url
};
db.Links.Add(enity);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(link);
}
這邊可以看到,透過ViewModel,我們只傳入實際讓使用者輸入的Property。假設那一天要減少一個Property,而轉換成為Entity的部分忘記改,在Compile的時候就會知道,這樣也可以減少bug的出現。假設還是用[Bind]的方式,那麼減少欄位忘記改在compile是不會報錯,因為[Bind]用的是string。
其實會和上面概念一樣,取得某一筆Entity資料之後,把它轉換成為ViewModel並且設定ViewModel Property顯示的值。然後,傳到View裡面。
看完ViewModel的好處,我們也需要看一下它的問題。
最主要問題在於,Entity和ViewModel之間的轉換。以上面的例子,這個轉換的邏輯不好通用。假設,今天我別的地方也需要把LinkCreateViewModel和Link 做轉換,我要怎麼共用這個轉換的邏輯?就算寫成通用方法,想想如果每一個ViewModel都寫一次,不是很浪費時間嗎?
針對這個問題,我們下一篇將介紹一個常用的套件,AutoMapper來解決。
透過這一篇我們瞭解到ViewModel的好處並且為什麼使用ViewModel。同時我們也注意到了ViewModel使用上面的不便利性。
下一篇將會介紹AutoMapper,看看AutoMapper是如何解決這個問題。