做了幾個項目,有接觸過MVC,不過不是用微軟定義的那套MVC的框架,是別人寫的一個很簡單的MVC框架。因此對於微軟的那個MVC框架,無論幾點零版本的。鄙人還不會用。近日從 lulu Studio 的系列博文 《ASP.NET MVC 入門系列教程》從頭學起。
先對項目簡單了解一下
在上圖中可以明顯的看到MVC那三個玩意了:代表M的模型(Model),代表V的視圖(View),代表C的控制器(Controller)。就是下圖這個經典的品字形圖,了解過MVC的肯定見過。
圖上原本有的一些文字就不列出來的,但光看這些東西還是對MVC一頭霧水的。至少請求的過程還不知道是什么回事。那就回到還沒用上MVC的WebForm說起,比較比較。
在WebForm時,瀏覽器往服務器發出請求時這樣的,發一個形如 http://localhost:4419/UI/Index.aspx的URL,服務器收到請求之后,就直接從根目錄下找到UI文件夾下的Index.aspx文件,經過Index.aspx.cs的相關處理(這處理有大有小,有的作一般的判斷,有的連接數據庫,還有其他的不列舉了)之后,生成了一個頁面返回給瀏覽器。大致如下圖
對於在MVC模式下則不相同了,首先,你若是用回上面那個請求的URL,即便是路徑是正確的,也是獲取不到期望得到的內容,它會返回一個"無法找到資源"的404錯誤。這是因為MVC框架的處理機制不同了。在MVC下多了個路由的機制,從瀏覽器發來的URL要符合路由的規格,經過路由的解析選擇相應的控制器,調用控制器里的Action,做完相關處理之后,返回一個視圖給瀏覽器。大致入下圖。
其實在項目中會有一個或多個控制器,它們都存放在Controller文件夾里面,以Controller作主文件名(也是類名)的后綴,Action其實是控制器里面的方法,Action是有一個或多個,當然這里的模型和視圖都是有一個或多個,在一個Action里頭調用的模型數量按需求定,>=0個。返回的視圖只能是一個。
這里的路由設置放在了Global.asax文件的RegisterRoutes方法里。
1 public static void RegisterRoutes(RouteCollection routes) 2 { 3 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 4 5 6 7 routes.MapRoute( 8 "Default", // 路由名稱 9 "{controller}/{action}/{id}", // 帶有參數的 URL 10 new { controller = "System", action = "Index", id = UrlParameter.Optional } // 參數默認值 11 ); 12 13 }
我這里設置了默認的控制器是System,就是Controller夾里的SystemController。從請求的URL來看,System/Index,是System控制器下的Index Action,也就是SystemController類下面的Index方法。
1 public class SystemController : Controller 2 { 3 // 4 // GET: /Index/ 5 6 public ActionResult Index() 7 { 8 return View("Index"); 9 } 10 11 //.........Other Actions 12 }
Action方法可以加上一些Attribute,例如HttpGet和HttpPost等,以限制在Get或者Post請求下才能訪問這個Action,Action還可以帶參數,不過帶了參數就要設置好相關的路由,否則進到方法里參數會為空的。每個Action方法都要返回一些東西,最簡單的就是"View();"就是返回與當前Action同名的視圖。當前是Index方法,那單純return View(); 就是返回一個Index.aspx頁面,如果要指定其他頁面,可以想上面那樣調用View方法的另一個重載,字符串的內容就是視圖的名稱。在這里return的還可以有其他東西:
-
ViewResult. 呈現視圖頁給客戶端。由View 方法返回.
-
RedirectToRouteResult. 重定向到另外一個Route。由RedirectToAction 和RedirectToRoute 方法返回.
-
RedirectResult. 重定向到另外一個URL。由 Redirect 方法返回.
-
ContentResult. 返回普通的內容。例如一段字符串。由 Content 方法返回.
-
JsonResult. 返回JSON結果。由 Json 方法返回.
-
EmptyResult. 如果Action必須返回空值,可以返回這個結果。Controller中沒有實現的方法,可以return new EmptyResult();
上面列出的都是繼承一個抽象類ActionResult,因此我們還可以自己定義一種ActionResult,但那方面鄙人還沒涉及到。
以上則把一些MVC的基礎知識介紹了一下,下面則通過一些實例來介紹其他方面,由於鄙人還是行入門了沒幾天,有說錯的還得請各位大俠指出;用的方法太笨拙的還請得各位前輩指點。
登陸
首先則是實現一個簡單的登陸功能。
用到了AdminModel,主要是驗證密碼,但是賬號密碼是寫死的。

1 public class AdminModel 2 { 3 public static bool Login(string no, string pw) 4 { 5 if (no == "admin" && pw == "123456") 6 return true; 7 return false; 8 } 9 } 10 11 public class AdminData 12 { 13 public int AdminID { get; set; } 14 15 public string AdminNo { get; set; } 16 17 public string AdminName { get; set; } 18 19 public string AdminPassword { get; set; } 20 21 }
定義的模型文件里存放了兩個類,秉承了以往開發三層架構項目時用到的實體類,這里也用上了,Data那個的則是實體類,與文件名同名的那個是處理一些邏輯的,比如登陸。
控制器里的Action方法定義如下,擁有兩個參數,一個是賬號id,另一個是密碼password,在Action里定義調用了Model的方法驗證賬號密碼的准確性,如果正確的就跳轉到MainView視圖,
用了RedirectToAction重定向到MainView的那個Action里,如果密碼不正確就逗留在原本的Index視圖里。
1 public ActionResult Login(string id, string password) 2 { 3 if ( AdminModel.Login(id, password)) return RedirectToAction("MainView"); 4 return View("Index"); 5 }
這樣的需要重新定義一個路由
1 routes.MapRoute("Login", "System/Login/{id}/{password}", new { controller="System", action="Login" });
URL的默認形式是"{controller}/{action}/{id}",控制器和行為都已經填上了System和Login了。
在視圖中原本想隨便拖幾個控件罷了,可是拖的button的事件用不了,就是由於MVC與WebForm的處理機制不同,以前WebForm單擊了按鈕,回傳給服務器,在生成新的html之前會在aspx.cs文件作一些處理,包括了事件響應。可現在MVC不同了,所有請求都到路由,再到控制器上的行為,即使在視圖的文件aspx上直接加C#的代碼都是行不通的。代碼是這樣的
<div style=" margin-left:auto; margin-right:auto; padding-top:64px; width:427px; height:186px; background-color:#33CCFF "> <div style="margin-left:auto; margin-right:auto; width:208px; "> 賬號: <input type="text" id="txtID" /> </div> <div style="margin-left:auto; margin-right:auto;width:208px"> 密碼: <input type="password" id="txtPW"/> </div> <div style="margin-left:auto; margin-right:auto;width:208px"> <input type="button" id="btnReset" value="Reset" class="btnClass" onclick="btnReset_Click()" /> <input type="button" id="btnLogin" value="Login" class="btnClass" onclick="btnLogin_Click()" /> <script type="text/javascript"> function btnReset_Click() { $("#txtID,#txtPW").val(""); } function btnLogin_Click() { var id = $("#txtID").val(); var pw = $("#txtPW").val(); location = "System/Login/" + id + "/" + pw; } </script> </div> </div>
這里只是用回原本html里的表單元素,驗證賬號密碼時直接把location改了就是了。附上界面的圖片
表格綁定
登陸成功之后跳轉到的是主界面,主界面上只有一個表格,列舉的是客戶信息用的是ViewData來把信息從行為方法傳遞到視圖上。由於以往用的web控件都用不了,只能用別的辦法做一個網格表出來,用了單值綁定。別的方法可以是js動態構造一個出來,或者用插件。在這里就嘗試一下這種沒用過的方式。
<script type="text/javascript"> $(function () { var time = new Date(); if (time.getTime() < 12) $("#time").text("上午好"); else if (time.getTime() < 18) $("#time").text("下午好"); else $("#time").text("晚上好"); }); </script> <div> <div id="header"> <%= ViewData["Admin"] %> , <label id="time"></label> </div> <table border="1" cellpadding="0" cellspacing="2"> <tr> <td class="hidden">id</td> <td>編號</td> <td>姓名</td> <td>性別</td> <td>出生年月</td> <td></td> </tr> <% List<MVCDemo.Models.UserData> dataSouce = ViewData["userList"] as List<MVCDemo.Models.UserData>; %> <%if(dataSouce!=null) %> <% foreach (MVCDemo.Models.UserData user in dataSouce) { %> <tr> <td class="hidden"> <%=user.UserID %> </td> <td> <%=user.UserNo %> </td> <td> <%=user.UserName %> </td> <td> <%=user.UserSex?"男":"女" %> </td> <td> <%= user.UserBirth==new DateTime()?"--":user.UserBirth.ToString("yyyy-MM-dd") %> </td> <td> <a href="/Customer/CustomerDetail/<%= user.UserID %>">查看</a> </td> </tr> <% } %> </table> </div>
附上界面的圖片
控制器的代碼和模型的代碼依次列出。
1 public ActionResult MainView() 2 { 3 List<UserData> list = UserModel.GetUserList(); 4 ViewData["Admin"]="admin"; 5 ViewData["userList"] = list; 6 return View(); 7 }

1 public class UserModel 2 { 3 private static List<UserData> list=new List<UserData>() 4 { 5 new UserData(){ UserID=1, UserName="Tom", UserNo="USR001", UserPassword="123456", UserSex=true,UserBirth=new DateTime(1990,1,1)}, 6 new UserData(){UserID=2,UserName="Tim" , UserNo="USR002", UserPassword="234567", UserSex=true,UserBirth=new DateTime(1992,2,1)}, 7 new UserData(){UserID=3,UserName="Mary" , UserNo="USR003", UserPassword="243567", UserSex=false,UserBirth=new DateTime(1993,4,5)}, 8 new UserData(){UserID=4,UserName="Kay" , UserNo="USR004", UserPassword="234547", UserSex=false,UserBirth=new DateTime(1996,5,4)}, 9 new UserData(){UserID=5,UserName="Ben" , UserNo="USR005", UserPassword="234567", UserSex=true} 10 }; 11 12 public static bool InitPassword(int id) 13 { 14 UserData user = list.Where(c => c.UserID == id).FirstOrDefault(); 15 if(user==null)return false; 16 user.UserPassword = "123456"; 17 return true; 18 } 19 20 public static List<UserData> GetUserList() 21 { 22 return list; 23 } 24 25 public static UserData GetUserDetail(int id) 26 { 27 UserData user = list.Where(c => c.UserID == id).FirstOrDefault(); 28 return user; 29 } 30 31 public static bool UpdateUser(UserData user) 32 { 33 UserData ent = list.Where(c => c.UserID == user.UserID).FirstOrDefault(); 34 if (ent == null) return false; 35 ent.UserName = user.UserName; 36 ent.UserSex = user.UserSex; 37 ent.UserBirth = user.UserBirth; 38 return true; 39 } 40 } 41 42 public class UserData 43 { 44 public int UserID { get; set; } 45 46 public string UserNo { get; set; } 47 48 public string UserName { get; set; } 49 50 public string UserPassword { get; set; } 51 52 public bool UserSex { get; set; } 53 54 public DateTime UserBirth { get; set; } 55 }
控制器的行為方法里頭,用了ViewData。在UserModel里面,用了一個List來存放用戶列表,免了用數據庫讀取數據庫那部分的繁瑣操作。
查看編輯客戶
上面的例子中,每一行的末尾都有一個"查看"的超鏈接,點擊了這個超鏈接就可以進入每個客戶的詳情頁面。這個詳情頁面包括了三個功能:初始化密碼,保存更改和返回。這些按鈕都是直接用表單元素,通過js更改location的方式來引致觸發器的相應的行為處理。其他的文本框就應用了MVC提供的helper的方法來生成html方法。視圖的代碼如下
<script type="text/javascript"> $(function () { if ('<%= Convert.ToBoolean( TempData["Result"]) %>' == "True") alert("Init password success!"); if ('<%= Convert.ToBoolean( TempData["editRes"]) %>' == "True") alert("Update Success!"); }); </script> <form id="form1" action='/Customer/UpdateCustomer/<%= ViewData.Eval("user.UserID") %>' method="post"> <div> 編號:<%= Html.Label(ViewData.Eval("user.UserNo").ToString()) %> <br /> 姓名:<%= Html.TextBox("UserName") %> <br /> 性別:<%--<%= Convert.ToBoolean( ViewData.Eval("user.UserSex"))?"男":"女" %>--%> <%= Html.RadioButton("UserSex",true, Convert.ToBoolean(ViewData.Eval("user.UserSex")), new {@name="sex" })%>男 <%= Html.RadioButton("UserSex",false, !Convert.ToBoolean(ViewData.Eval("user.UserSex")), new {@name="sex" })%>女 <br /> 出生年月:<%= Html.TextBox("UserBirth", Convert.ToDateTime( ViewData.Eval("user.UserBirth")).ToString("yyyy-MM-dd")) %> <br /> <input type="button" id="btnInitPW" value="InitPassword" onclick="btnInitPW_Click()" /> <input type="button" id="btnSave" value="SaveData" onclick="btnSave_Click()" /> <input type="button" id="btnBack" value="Back" onclick="btnBack_Click()" /> <script type="text/javascript"> function btnInitPW_Click() { location = '/Customer/ResetPassword/<%= ViewData.Eval("user.UserID") %>'; } function btnSave_Click() { form1.submit(); } function btnBack_Click() { location = "/System/MainView"; } </script> </div> </form>
附上界面的圖片
由於這里要用到數據的提交,用了個表單,而且所有編輯的框都是用helper生成的,name屬性應該不用自己填了。以上這部分的代碼覺得有幾個地方不對勁,但是資歷尚淺還不知道如何改進,第一是表單提交的URL里頭有用到單值綁定;第二是保存用戶信息和初始化密碼這兩個更改操作之后返回的信息是使用了TempData把操作結果返回給視圖,在視圖上通過js處理把結果呈現給客戶;第三是綁定數據時有些糾結,行為方法里面return View()時附帶了要綁定的對象,在這里就是user這個對象,如果要獲取user的某個屬性作一些處理,好比上面的UserSex和UserBirth,單純用helper生成表單元素時綁定行不通,鄙人只好再多用一個ViewData。 希望各位有什么意見或者建議多提點一下。
下面則是控制器的代碼
1 public ActionResult ResetPassword(int id) 2 { 3 bool result= UserModel.InitPassword(id); 4 TempData["Result"]=result; 5 return Redirect("/Customer/CustomerDetail/" + id); 6 } 7 8 public ActionResult UpdateCustomer(int id) 9 { 10 bool updateResult = true; 11 UserData user = UserModel.GetUserDetail(id); 12 try 13 { 14 15 UpdateModel(user, new string[] { "UserNo", "UserName", "UserSex", "UserBirth" }); 16 updateResult= UserModel.UpdateUser(user); 17 } 18 catch 19 { 20 updateResult = false; 21 } 22 TempData["editRes"] = updateResult;
23 ViewData["user"] = user; 24 return View("CustomerDetail",user); 25 }
要獲取表單提交過來的數據,用15行的代碼就行了,當然可以用更原始的方式Request.Form[""]獲取。
好了,這篇博文介紹到此,作為MVC的初次涉足,做到這樣知道還很不像樣,以后多加探究。大年初四
代碼下載MVCDemo.rar