MVC系列文章終於開始了,前段時間公司項目結束后一直在封裝一個html+ashx+js+easyui的權限系統,最近差不多也完成了,遲些時候會分享源碼給大家。當然這個MVC系列結束后如果時間允許還會另寫一個MVC + EF + ExtJs的權限系統作為總結,希望有興趣的朋友保持關注。
本系列文章使用的環境是:VS2010 + 4.0 Framework、sql 2008企業版、MVC3、EF4.1
注:VS2010不帶MVC3,ASP.NET MVC 3 RTM下載地址。裝的時候vs不要打開,先裝MVC3,再裝漢化包。安裝中不要強制取消,就算取消也要等回滾完,否則下次vs啟動不了。
一、ASP.NET MVC 3 小試牛刀
新建項目 - 創建mvc3 web應用程序 - 模板選空、視圖引擎Razor、不使用HTML5,建好項目后,解決方案是這樣的:
和傳統的webForm項目結構完全不一樣,這里不寫任何代碼直接Ctrl+F5跑下程序,會得到:
webForm里直接運行項目會列出已有的文件讓你點選然后運行,當然也可以在頁面上右鍵 - 在瀏覽器中查看,mvc項目里無法通過在.cshtml視圖文件上右鍵 - 在瀏覽器中查看的方式運行頁面,而是通過【路由】找控制器Controller里的Action,Action再調用指定的視圖View呈現頁面。因此運行MVC項目只能Ctrl+F5或者F5調試的方式運行程序,要看不同的頁面需要在路由里進行調整。后續會有文章詳細講解路由,這里僅快速理解下MVC:
M Model:實體模型 / 視圖模型(ViewModel);
V View:視圖。簡單說就是html頁面,這個html頁面接收強類型的數據,方便展示數據;
C Controller:控制器。處理瀏覽器發過來的請求,根據url過來的路徑去控制器里尋找對應的Action響應瀏覽器請求。
解決上面404錯誤問題先得看看Global.asax里默認路由的定義:
routes.MapRoute( "Default", // 路由名稱 "{controller}/{action}/{id}", // 帶有參數的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 參數默認值 );
只有一個路由,按照路由匹配自上到下的順序,那么本程序使用的就是這個名為Default的路由。根據這個路由的定義瀏覽器有類似這種Url:localhost:****/abc/def/e 請求過來自然就和這個路由匹配({controller}/{action}/{id})上了,那么找的是名稱為abc的Controller,調用abcController里的名為def的Action,呈現的是def這個Action里指定的視圖View。當然這種Url:localhost:**** 請求過來也能和這個名為Default的路由匹配上,因為這個默認路由定義了默認值:HomeController、Index Action、Id值可選。
到這里就很明顯知道為何是404了,因為既沒有HomeController,也沒有Index這個Action,同時也沒有任何視圖View。在解決方案里的Controller目錄上右鍵 - 創建控制器,為了和這個路由的定義搭上,控制器的名字就是HomeController,是一個空控制器。新建就好了后打開HomeController.cs:
public ActionResult Index() { return View(); }
HomeController里默認就有一個叫Index的Action,並且這個Action的返回值不是void,也不是bool,也不是string,而是ActionResult。這個ActionResult就可以理解是視圖,返回一個視圖給Action(上后面的代碼直接return View();的話就是返回默認視圖,即和Action同名的Index),然后響應給控制器,最終顯示給用戶。
注:常用的還有JsonResult、RedirectResult等等。具體點這里
先不返回視圖給Action,修改下返回值類型並返回一個string試試:
public string Index() { return "hElLo WOrLd!!!"; }
很明顯,返回的不是ActionResult了,而是string,先按F6編譯下再Ctrl+F5跑下程序,顯示:
很容易理解:默認路由找HomeController下的名為Index的Action拿到數據顯示到瀏覽器,這里拿到的數據就是Index這個Action返回的字符串。右鍵查看下頁面的源碼,很純粹的源碼,就一個“hElLo WOrLd!!!”字符串。
注意下上圖的url:localhost:1030,其實完整的是localhost:1030/home/index(默認路由里已經指定了,所以可以省略),看的出路由里定義之后就體現在瀏覽器url里了:先找Home,再找Home下的Index。
再修改方法:
public ActionResult Index() { return View(); }
然后在這個方法里右鍵 - 添加視圖 - 默認就是和Action同名的Index視圖,不使用模板。添加后項目的Views目錄下就多一個Home子目錄,目錄里有一個Index.cshtml,修改其中的代碼為:
@{ Layout = null; } hElLo WOrLd!!!
Layout=null就是不使用模板。F6編譯下項目再跑下程序發現和之前顯示一樣,查看源碼也一樣。這里很好理解:Controller找Action,Action調用對應的View呈現頁面
完整的url是:localhost:1030/home/index 這里跟上面稍有不同:上面的index這個action直接就返回了一個string,未調用任何試圖,這里的index顯示的是view返回的string。
二、向視圖中輸出動態內容
asp、aspx等動態頁面相比於靜態的html好處就是可以根據傳遞的參數動態的顯示內容,視圖.cshtml自然也可以,可以通過ViewBag來實現,修改action:
public ActionResult Index() { int hour = DateTime.Now.Hour; ViewBag.Greeting = hour < 12 ? "早上好" : "下午好"; return View(); }
通過一個三元表達式給ViewBag.Greeting賦了一個值,然后在前台View加個@符號就可以調用了:@ViewBag.Greeting WOrLd!!!
F6編譯下項目再跑下程序,如願的顯示了:下午好 WOrLd!!!
注:前台和后台的ViewBag打點都是無智能提示的,只要前后台的名稱一樣即可調用。
三、稍復雜的例子
ok,到這基本就已經知道mvc是怎么玩的了,現在來一個稍復雜的例子,模擬邀請客人參加party的小程序:
先到解決方案的Models目錄下添加一個客人信息類:
/// <summary> /// 客人信息類 /// </summary> public class GuestResponse { public string Name { get; set; } public string Email { get; set; } public string Phone { get; set; } public bool? WillAttend { get; set; } //是否參加(可空類型) }
修改之前的Index視圖(Index.cshtml):
@{ Layout = null; } <!DOCTYPE html> <html> <head> <title>Index</title> </head> <body> <div> @ViewBag.Greeting, WOrLd!!! <p> 我們將舉辦一個Party<br /> </p> @Html.ActionLink("填表參加", "RsvpForm") </div> </body> </html>
跟普通html頁面不同的是Rozar語法里所有的頁面元素都是通過html輔助方法生成的,這里的@Html.ActionLink("填表參加", "RsvpForm")就是生成一個a標簽。Rozar語法簡單清爽,寫個@符號就可以開始在頁面上寫代碼了。
根據智能提示可知,第一個參數"RSVP Now"是a標簽的文字,第二個參數"RsvpForm"是這個a標簽跳轉的地址,通過Action實現跳轉自然需要添加這個Action,否則點擊又找不到視圖了:
public ViewResult RsvpForm() { return View(); }
一看到return View();自然返回的是默認的視圖,即RsvpForm。繼續去添加這個視圖:在這個ResvpForm這個Action內右鍵 - 添加視圖 - 勾選上“創建強類型視圖” - 選擇GuestResponse模型類(如果找不到就先按F6編譯下項目)- 不使用模板類和腳本庫,生成的View是:
@model SimpleMVC3Demo.Models.GuestResponse @{ Layout = null; } <!DOCTYPE html> <html> <head> <title>RsvpForm</title> </head> <body> <div> </div> </body> </html>
注意第一行:@model SimpleMVC3Demo.Models.GuestResponse 這就是勾選強類型視圖的好處,直接把強類型拋到了View里,其實手動寫這一行也是一樣的效果(同樣需要編譯項目)
繼續完善RsvpForm這個視圖,在body里去掉div然后添加表單和一些文本框下拉列表供用戶填寫信息:
@using (Html.BeginForm("RsvpForm", "Home", FormMethod.Post)) { <p> Your name: @Html.TextBoxFor(x => x.Name) </p> <p> Your email: @Html.TextBoxFor(x => x.Email)</p> <p> Your phone: @Html.TextBoxFor(x => x.Phone)</p> <p> Will you attend? @Html.DropDownListFor(x => x.WillAttend, new[] { new SelectListItem() {Text = "我會參加Party", Value = bool.TrueString}, new SelectListItem() {Text = "我不參加Party", Value = bool.FalseString} }, "Choose an option") </p> <input type="submit" value="提交" /> }
這里的x => x.屬性方式其實就是lambda表達式的寫法,這里不用x,用任何字母都可以,前提是必須已經把強類型的拋到View里。
這就是這個表單提交的地址,其實就等於webForm里的:
<form action="/Home/RsvpForm" method="post"> <input type="text" name="username" /> <input type="text" name="userpwd" /> <input type="submit" value="提交" /> </form>
這個Form提交的地址是指定的HomeController下的RsvpForm,之前已經有一個:
public ViewResult RsvpForm() { return View(); }
其實提交表單處理的不是這個Action,這個是get請求的Action,需要另加一個post的Action(mvc里默認的action響應的是get方式的請求,用於請求頁面內容,響應post請求的action一般是提交表單等):
[HttpPost] public ViewResult RsvpForm(GuestResponse guestResponse) { return View("Thanks", guestResponse); }
這只是個簡單的Action,並沒有對用戶提交的表單內容進行保存,而是直接返回了一個Thanks視圖,並且把用戶對象也就是用戶在表單里填寫的用戶信息傳到了Thanks這個視圖,來看看這個用戶對象guestResponse如何傳遞的:在這個HttpPost下面按F9加個斷點,然后按F5調試下項目,當提交表單時,這個對象其實就已經有屬性了:
很明顯這個對象已經有值了,繼續在Views目錄下的Home目錄上右鍵 - 添加視圖 - 視圖名稱Thanks - 勾選強類型視圖 - 不使用模板頁和腳本庫。ok,這個視圖也拿到了傳遞過去的用戶對象,其實就可以在body里輸出一些友好的回復:
<div> <h1> Thank you, @Model.Name!</h1> @if (Model.WillAttend == true) { @:你能來真的太好了,飲料和零食都為你准備好了! } else { @:很遺憾你不能來。 } </div>
注意看,Razor視圖引擎里可以直接寫if else邏輯語句。
寫到這里該跑下程序了,再次F6編譯下后按Ctrl + F5跑下程序。
第一個頁面:
源碼:
<div> 下午好, WOrLd!!! <p> 我們將舉辦一個Party<br /> </p> <a href="/Home/RsvpForm">填表參加</a> </div>
第二個頁面:
源碼:
<form action="/Home/RsvpForm" method="post">
<p> Your name: <input id="Name" name="Name" type="text" value="" /> </p> <p> Your email: <input id="Email" name="Email" type="text" value="" /></p> <p> Your phone: <input id="Phone" name="Phone" type="text" value="" /></p> <p> Will you attend? <select id="WillAttend" name="WillAttend">
<option value="">請選擇</option> <option value="True">我會參加Party</option> <option value="False">我不參加Party</option> </select> </p> <input type="submit" value="提交" /> </form>
第三個頁面:
源碼:
<div> <h1> Thank you, 汪傑!</h1> 你能來真的太好了,飲料和零食都為你准備好了! </div>
注意看上面兩個圖的url地址一樣,都是:localhost:1031/Home/RsvpForm
但是頁面顯示的內容不同,因為一個是get請求,一個提交表單是post請求,提交后被導向另一個視圖了。
驗證表單:剛才的表單就算任何都不填寫也可以提交,顯然不符合常理,驗證需要三步,第一去模型類里定義驗證規則:
[Required(ErrorMessage = "姓名不能為空")] public string Name { get; set; } [Required(ErrorMessage = "郵箱地址不能為空")] [RegularExpression(".+\\@.+\\..+", ErrorMessage = "請輸入合法的郵箱地址")] public string Email { get; set; } [Required(ErrorMessage = "電話不能為空")] public string Phone { get; set; } [Required(ErrorMessage = "請選擇是否參加")] public bool? WillAttend { get; set; } //是否參加(可空類型)
這是Data Annotation標注的形式來驗證的,如果還不知道Data Annotation為何物,建議先去了解一下
注:需要引用命名空間System.ComponentModel.DataAnnotations;
第二去控制器里判斷是否通過驗證:
if (ModelState.IsValid) { return View("Thanks", guestResponse); } else { return View(); }
直接調用的ModelState.IsValid方法來驗證模型是否通過驗證。當然到這里還不算結束,第三去View里應用驗證:
在using語句下面加上:@Html.ValidationSummary() 表示此表單需要驗證,這時候如果表單數據不合法則不會被導向到Thanks視圖。
到此,再跑程序生成的html頁面就又不同了,會有驗證信息在里面。一旦用戶輸入的內容不符合在模型類里定義的規則,那么驗證信息就會顯示出來。看源碼:
<form action="/Home/RsvpForm" method="post">
<div class="validation-summary-valid" data-valmsg-summary="true">
<ul><li style="display:none"></li></ul>
</div>
<p> Your name: <input data-val="true" data-val-required="姓名不能為空" id="Name" name="Name" type="text" value="" /> </p> <p> Your email: <input data-val="true" data-val-regex="請輸入合法的郵箱地址" data-val-regex-pattern=".+\@.+\..+" data-val-required="郵箱地址不能為空" id="Email" name="Email" type="text" value="" /></p> <p> Your phone: <input data-val="true" data-val-required="電話不能為空" id="Phone" name="Phone" type="text" value="" /></p> <p> Will you attend? <select data-val="true" data-val-required="請選擇是否參加" id="WillAttend" name="WillAttend">
<option value="">請選擇</option> <option value="True">我會參加Party</option> <option value="False">我不參加Party</option> </select> </p> <input type="submit" value="提交" /> </form>
高亮錯誤信息行其實很簡單,直接在RsvpForm視圖里添加一個默認css的引用:
<link rel="Stylesheet" href="@Href("~/Content/Site.css")" type="text/css"/>
再提交非法表單信息就高亮了錯誤行:
其實提交表單的一剎那瀏覽器還是有刷新的,說明這是個服務器端驗證,實際項目中更應該服務端和客戶端雙驗證,后續文章會有詳細介紹。
本文源碼