ASP.NET MVC 3 豬走路範例 (2)
5 |
上回約略看過Controller與View,這回輪到Model,就能完成MVC大三元(謎之聲: 可惡! 好冷...)。由於MVC 3有不少依Model自動產生View的機制,故開發時從Model入手會較省力(註: 如果客戶對UI的客製需求高則則省不了多少工,故勿存有過於美好幢景)。
為了讓範例單純一點,決定不把資料庫扯進來,寫一個用資料夾混充資料庫的PlayerModel:
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.IO;
namespace FirstMvc.Models
{
//使用檔案當成資料來源的玩家資料物件
//先不牽扯資料庫可以更單純一點
public class PlayerModel
{
[Required]
[Display(Name = "代號")]
[RegularExpression("[A-Za-z0-9]{3,8}",
ErrorMessage = "限定為3-8個英文或數字")]
public string Name { get; set; }
[Required]
[Display(Name = "分數")]
[Range(0, 65535,
ErrorMessage = "範圍: 0 - 65535")]
public int Score { get; set; }
#region 儲存相關函數
static string storageFolder = "c:\\temp\\players";
static string getFilePath(string name)
{
return Path.Combine(storageFolder, name + ".txt");
}
private string FilePath
{
get { return getFilePath(Name); }
}
#endregion
#region 模擬 新增/修改/刪除/查詢 功能
//儲存
public void Save()
{
File.WriteAllText(FilePath, Score.ToString());
}
//讀取
public static PlayerModel Read(string name)
{
string file = getFilePath(name);
if (File.Exists(file))
return new PlayerModel()
{
Name = name,
Score = int.Parse(File.ReadAllText(file))
};
else
return null;
}
//刪除
public void Delete()
{
if (File.Exists(FilePath)) File.Delete(FilePath);
}
//清單
public static List<string> GetPlayerNames()
{
//列舉所有檔案名稱(亦即玩家名稱),傳回字串陣列
return
Directory.GetFiles(storageFolder, "*.txt")
.Select(o => Path.GetFileNameWithoutExtension(o)).ToList();
}
#endregion
}
}
PlayerModel其實只有兩個屬性,Name及Score,其餘程式碼則用來模擬新增、修改、刪除等動作。注意[Require] [Regular Expression(…)] [Range(…)]等System.ComponentModel.DataAnnotations命名空間的Attribute,會被當成檢核資料是否正確的依據,甚至在Client端也用會同樣的檢核邏輯以Javascript進行資料驗證。把PlayModel.cs放進Models目錄下(實務上,我們甚至會將Model類別切出一顆獨立DLL類別庫),接著,預想一下使用者的操作流程:
- 先連上httq://server/home/create (懶得再開一個新的Controller,所以就寄生在HomeController.cs中,只新增一個Create Action),在HTML表單輸入Name及Score
- 按Submit送出表單,POST回httq://server/home/create,將結果存成檔案
- 懶得想過場,儲存完畢直接回到httq://server/home/index以求省事
我們在HomeController裡加上兩個Action,Create()及Create(PlayerModel player),分別標上[HttpGet]及[HttpPost]兩個ActionFilterAttribute,如此可區分出用httq://server/home/create時會先連上Create(),按Submit送出時再連到Create(PlayerModel player) (因為是HTTP POST)。
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
[HttpGet] // httq://server/home/create, 新增資料用
public ActionResult Create()
{
//預設會對映到/Views/Home/Create.cshtml
return View();
}
[HttpPost] // httq://server/home/create 送出表單時
public ActionResult Create(PlayerModel player)
{
//前方送來的資料會自動對應到Player上(很神奇吧?)
if (ModelState.IsValid) player.Save();
//轉到httq://server/home/index
return RedirectToAction("Index");
}
}
Controller設好,接下來要產生View。在Visual Studio 2010這種上流社會華麗IDE環境中,一定有不用徒手從頭寫View的方法吧? 沒錯,在Action按下右鍵:
就有精靈現身幫你實現願望... 如下圖,在Model class裡可以選到PlayerModel(記得專案要先Build一次才會出現在清單中),接著指定Scaffold template為Create(共有Create、Details、Delete、Edit、Empty、List等選擇),按下Add,VS2010會自動生出/Views/Home/Create.cshtml來。
自動產生的Create.cshtml內容不少,還有一堆validate相關的字眼,莫非它也內含前端驗證的功能?
試試便知! (註: MVC Project是Web Application Project,更改後記得要先Build再測試,修改的程式碼才會完整更新,但Razor View Engine的部分是動態編譯的,更動.cshtml後,倒是可以儲存後就生效)
很強吧! 這個自動產生的View已具備Javascript欄位檢核功能,其檢核規則就是依我們在PlayerModel上用DataAnnotations Attribute定義出來的[Require] [Range(…)]等條件,而在Create(PlayerModel player)中,我們判斷ModelState.IsValid等同做了Server端的檢核。換句話說,只需在Model設定限制,就一口氣做到了Client Side與Server Side的資料驗證!! (註: 我又來潑冷水了,當UI的客製需求高、驗證邏輯複雜時,當然不可能點一點滑鼠就靠內建功能滿足客戶所有需求,但必須要說,ASP.NET MVC架構頗具彈性,內建功能不足的部分,多半可以靠自行開發組件將其補足,原則上只要投入一些時間與研發人力,要豬飛天都是有可能的)
實地測試,輸入有效的代號(Jeffrey)及分數(32767),按下Create鈕,就可以在c:\temp\players目錄下驗收內容為32767的Jeffrey.txt檔案,很棒吧? 等等,Create(PlayerModel model)裡連個Request[“Name”]什麼都沒看到,資料是怎麼接進來的? 要解答這個疑惑,不妨在if (ModelState.IsValid)處加上中斷點:
如上圖所示,很神奇吧! 前端送來的HTML表單欄位資料被自動轉成PlayerModel物件屬性,這就是神奇的Model Binding機制,透過這種方式,程式碼變得很簡潔吧!
又到了豬腳按摩時間,下篇再續。
Comments
# by 大力
請教黑大,文中有句「實務上,我們甚至會將Model類別切出一顆獨立DLL類別庫」?意思是說,Model 放到另外一個專案來開發,方便將來移植是嗎?
# by Jeffrey
to 大力,切成獨立專案編譯成DLL的理由蠻多的,有時是為了分工(Model與UI不同人寫),有時是為了切割完整的管理或測試單位,依我自己的經驗,切割成獨立DLL的最大好處是方便不同的專案引用,例如: 後台與前台分成兩個Web App專案,而帳務Model二者共用,切成DLL專案可以避免將Model Class程式檔要複製成兩份放在兩個專案中,衍生日後維護需同步修改兩處的困擾。
# by 大力
感謝黑大指點
# by Saint
請問這一個Method 「GetPlayerNames」是否要改成Static呢?
# by Jeffrey
to Saint, 是的,應為static,程式範例漏了,謝謝指正。