上一篇我講了ASP.MET MVC的基礎概念,我相信從上一篇,我們可以知道MVC的執行過程。這一篇我們開始講解Model。我們知道,在我們的應用程序中,大多時候是在遵循業務邏輯通過UI操作數據。所以這里按照我們上一篇講的分離關注點的觀點。我們至少可以把程序分為三部分,UI,邏輯和數據。業務邏輯由我們根據具體的領域來實現,UI其實從根本就是展現數據,收集數據。我們的業務邏輯,操作的其實也還是數據。所以這一篇,我們從數據開始,因為我個人覺得,數據是最基本的。
什么是模型
其實我們從ASP.NET三層時代就開始接觸模型的概念,只是那個時候我們通常稱之為實體,這里我們簡單的談一下實體的概念。在談到現代軟件開發的時候,我們聽的很多的一個詞就是面向對象,簡單點理解,就是程序模擬現實中或者虛擬物體的數據和行為來達到完成現實中的既定任務。舉個例子來說,我們人。在現實中,人會有一些特征,比如身高:170cm,年齡:18。會說話,會走路,會跳,會跑。那么在面向對象的應用程序中,我們可以定義一類物體所能具有的特征。然后根據定義的這些特征去產生具體的物體。就像餅干模具和餅干的關系。餅干模具是定義了我們能生產出什么形狀的餅干。我們也可以通過餅干模具生產出特定於模具的餅干。在程序中,就是類和對象。類是定義了對象能包含什么樣的數據和行為。而對象則是實際包含這些數據和行為。而不同的領域所關注的特性是不一樣的。人這個物體,在銀行領域可能會關注你的信譽,存款等等特性。而在公司員工管理領域,可能關心的是你的年齡,技術方向等等。所以面對不同的領域,我們需要抽象出不同的類型特征。簡單概括,模型就是面對特定領域抽象出的具有某些領域特定的對象。我們可以增加對象,修改對象,刪除對象,查看對象,最后持久化對象。說到底,我們的對象是包含數據的,所以,我們操作的也就是數據。
Model
在ASP.NET中有兩種模型,一種是對象領域的,一種是面向視圖的。面向領域即數據根據特定的業務邏輯建模,面向視圖則是根據UI需要的數據來建模。這兩種模式各有好壞。我們可以混合使用,后面我們會介紹到。
這里,我們通過一個功能來詳細講解Model在MVC中怎么使用。我們要實現的功能是用戶登錄,登陸之后用戶可以看到一些新聞和產品在首頁。這里根據我們的業務邏輯,我們至少需要三個模型:用戶,新聞,產品。我們創建一個MVC項目。具體步驟見上一篇MVC基礎。接着,我們在Model文件夾中添加以下三個類。
UserInfo
1 public class UserInfo 2 { 3 private int _userInfoId; 4 5 public virtual int UserInfoId 6 { 7 get { return _userInfoId; } 8 set { _userInfoId = value; } 9 } 10 11 private string _userName; 12 13 public virtual string UserName 14 { 15 get { return _userName; } 16 set { _userName = value; } 17 } 18 19 private string _password; 20 21 public virtual string Password 22 { 23 get { return _password; } 24 set { _password = value; } 25 } 26 27 private bool _isLogin; 28 29 public virtual bool IsLogin 30 { 31 get { return _isLogin; } 32 set { _isLogin = value; } 33 } 34 }
NewsInfo
1 public class NewsInfo 2 { 3 private int _newsInfoId; 4 5 public virtual int NewsInfoId 6 { 7 get { return _newsInfoId; } 8 set { _newsInfoId = value; } 9 } 10 11 private string _newsTitle; 12 13 public virtual string NewsTitle 14 { 15 get { return _newsTitle; } 16 set { _newsTitle = value; } 17 } 18 19 private string _newsContent; 20 21 public virtual string NewsContent 22 { 23 get { return _newsContent; } 24 set { _newsContent = value; } 25 } 26 27 private DateTime _createTime; 28 29 public virtual DateTime CreateTime 30 { 31 get { return _createTime; } 32 set { _createTime = value; } 33 } 34 }
ProductInfo
1 public class ProductInfo 2 { 3 private int _productInfoId; 4 5 public int ProductInfoId 6 { 7 get { return _productInfoId; } 8 set { _productInfoId = value; } 9 } 10 11 private string _productName; 12 13 public string ProductName 14 { 15 get { return _productName; } 16 set { _productName = value; } 17 } 18 19 private string _productDescription; 20 21 public string ProductDescription 22 { 23 get { return _productDescription; } 24 set { _productDescription = value; } 25 } 26 27 private DateTime _createTime; 28 29 public DateTime CreateTime 30 { 31 get { return _createTime; } 32 set { _createTime = value; } 33 } 34 }
當我們建好了模型,現在就需要考慮怎么操作這些模型。一般來說我們有如下幾種方式
- 數據庫,使用傳統的ADO.NET操作數據。
- EF
- 服務調用
我們這里不介紹第一種方式,我們使用第二,三種方式來結合model操作數據。這里我們額外介紹一個概念叫做基架,基價可以為我們的程序生成樣板代碼,比如增加,修改,刪除,查找,基架會按照我們定義的模板生成代碼,當然,這個模板我們是可以修改的。這里,我們就先使用基架來完成我們上面的第二種方式,使用EF。
使用基架的方式是我們添加一個控制器,我們可以看到模板項,在模板項里面有如下幾個選項
- 空MVC控制器:這里會在controller里面幫我們生成一個Index操作。
- 包含讀寫操作和視圖的MVC(使用EF):這里會幫我們生成Index,Details,Create,Edit,Delete操作,並且生成視圖。而且還會幫我們生成數據操作的代碼
- 包含讀寫操作:幫我們生成Index,Details,Create,Edit,Delete操作,但是沒有實際的代碼,不會生成視圖。
我們使用第二種來生成數據操作代碼。
這里我就不過多的概述EF的使用方式,因為我們這里主要的思想是操作數據的方式,而不是特定於一種詳細的數據操作方式。如果對EF有興趣,請參考園子里EF的文章。第一步,我們需要一個數據上下文。所謂的數據上下文,其實就是一個數據的進口與入口。我們通過這個上下文來操作數據。因為這里我們使用的是EF4.1.這個版本的EF是隨MVC3一起發布的。所以,這里我們新建一個數據上下文,新建數據上下文的方式是繼承一個名為DbContext的類。
1 public class WebSiteContext:DbContext 2 { 3 public DbSet<UserInfo> Users { get; set; } 4 public DbSet<NewsInfo> News { get; set; } 5 public DbSet<ProductInfo> Products { get; set; } 6 }
這個類很簡單,我們聲明了3個類型為DbSet的屬性。我們來看看這個類型的解釋
// 摘要: // 表示用於執行創建、讀取、更新和刪除操作的類型化實體集。DbSet 不是公共可構造的,只能從 System.Data.Entity.DbContext // 實例創建。 // // 類型參數: // TEntity: // 定義集的類型。該類型可以是派生類型以及基類型。
其實摘要里面已經說得很清楚,執行增,刪,改,查詢就是得到這個集合。這里順便多提一個概念,就是EF的使用方式
- 數據優先:數據優先的方式是指,我們程序的數據從數據庫開始,也就是說,數據庫的設計是第一位。我們在程序里使用數據里的數據結構生成實體模型
- Model優先:Model優先是指程序的數據模型從EF的Model設計開始。最后使用Model的數據結構生成數據庫的數據結構,這種結構不一一對應的,我們使用Mapping建立模型屬性與數據庫字段的關系
- 代碼優先:代碼優先是指可以在不創建數據庫,也不創建EF Model的情況下,編寫純C#類。因為EF能夠理解我們的類,並根據我們的配置串在正確的位置幫我們生成數據存儲實例。
這里我們使用的就是代碼優先,另外還有一點我們需要注意,就是代碼優先約定。我們前面提到過這個概念,MVC遵循了許多約定優先於配置的概念,這里也是一樣,假設我們有一個UseInfo類,那么EF就會假設把數據存儲到在數據庫中一個名為UserInfo的表中。如果要存儲的對象有一個名為ID的屬性,那么EF就假這個值為主鍵值,並把這個值賦給sqlserver中對應的自動遞增標識列。這里是EF的概念,如果想了解更多,請參考其他關於EF的文章。好了,前面我們已經創建了我們的數據上下文。現在我們來應用這個數據上下文生成我們的控制。因為前面我們已經完成了必要的前期工作,完成了我們的Model,完成了我們的數據操作方法(數據上下文)。下面我們就開始完成我們的Controller。

這里我們使用了基架是包含讀寫和視圖,並使用EF。我們為我們的Controller選定一個模型類,這里我們新建的是一個Login控制器,所以,模型是我們的UserInfo。數據上下文為我們剛剛新建的數據上下文。需要注意的一點是,這里是完全限定名。即我們需要加上命名空間。在高級選項里有一些關於View的設置,比如自動添加腳本引用,是否引用布局。這里根據個人程序的設定。點擊添加就完成了我們Controller的創建,而且,我們可以看到,我們不僅創建了Controller,並且,VS還幫助我們創建了對應的View和Action。
我們其實是可以刪除生成的View,因為在很多時候,VS幫我們生成的View是不符合我們自己程序的要求。所以,這里我們刪除Login下的所有的View。當然,除了Index View。因為這個View是我們需要使用的。我們再來看看Controller的代碼。
1 public class LoginController : Controller 2 { 3 private WebSiteContext db = new WebSiteContext(); 4 5 // 6 // GET: /Login/ 7 8 public ActionResult Index() 9 { 10 return View(db.Users.ToList()); 11 } 12 13 public ActionResult Details(int id = 0) 14 { 15 UserInfo userinfo = db.Users.Find(id); 16 if (userinfo == null) 17 { 18 return HttpNotFound(); 19 } 20 return View(userinfo); 21 } 22 23 public ActionResult Create() 24 { 25 return View(); 26 } 27 28 [HttpPost] 29 public ActionResult Create(UserInfo userinfo) 30 { 31 if (ModelState.IsValid) 32 { 33 db.Users.Add(userinfo); 34 db.SaveChanges(); 35 return RedirectToAction("Index"); 36 } 37 38 return View(userinfo); 39 } 40 41 public ActionResult Edit(int id = 0) 42 { 43 UserInfo userinfo = db.Users.Find(id); 44 if (userinfo == null) 45 { 46 return HttpNotFound(); 47 } 48 return View(userinfo); 49 } 50 51 [HttpPost] 52 public ActionResult Edit(UserInfo userinfo) 53 { 54 if (ModelState.IsValid) 55 { 56 db.Entry(userinfo).State = EntityState.Modified; 57 db.SaveChanges(); 58 return RedirectToAction("Index"); 59 } 60 return View(userinfo); 61 } 62 63 public ActionResult Delete(int id = 0) 64 { 65 UserInfo userinfo = db.Users.Find(id); 66 if (userinfo == null) 67 { 68 return HttpNotFound(); 69 } 70 return View(userinfo); 71 } 72 73 [HttpPost, ActionName("Delete")] 74 public ActionResult DeleteConfirmed(int id) 75 { 76 UserInfo userinfo = db.Users.Find(id); 77 db.Users.Remove(userinfo); 78 db.SaveChanges(); 79 return RedirectToAction("Index"); 80 } 81 82 protected override void Dispose(bool disposing) 83 { 84 db.Dispose(); 85 base.Dispose(disposing); 86 } 87 }
這里幫我們生成了對應的增,刪,改,查的方法。在Login操作中,我們是不需要增加,刪除,和修改的。我們需要的是通過用戶名,密碼,和登陸狀態來驗證用戶的登陸。所以,這里我們刪除不必要的代碼然后增加我們需要的方法。
1 public class LoginController : Controller 2 { 3 private WebSiteContext db = new WebSiteContext(); 4 5 public ActionResult Index() 6 { 7 return View(); 8 } 9 10 [HttpPost] 11 public ActionResult Login(UserInfo userInfo) 12 { 13 if (ModelState.IsValid) 14 { 15 UserInfo user = db.Users.FirstOrDefault(u=>u.UserName==userInfo.UserName&&u.Password==userInfo.Password); 16 if (user!=null) 17 { 18 if (user.IsLogin == false) 19 { 20 return RedirectToAction("Index","Home"); 21 } 22 ModelState.AddModelError("","用戶已經登陸"); 23 return View("Index",userInfo); 24 } 25 } 26 ModelState.AddModelError("", "該用戶沒有注冊,請注冊用戶"); 27 return View("Index", userInfo); 28 } 29 30 protected override void Dispose(bool disposing) 31 { 32 db.Dispose(); 33 base.Dispose(disposing); 34 } 35 }
接着,我們需要替換我們的IndexView,因為。我們這里需要的方法是登陸,而默認基架生成的代碼並沒有給我們提供這個方法。所以,這里我們需要自己定義我們的Index View
1 @model ModelInMVC.Models.UserInfo 2 3 @{ 4 ViewBag.Title = "Login"; 5 } 6 7 <h2>Login</h2> 8 @using(Html.BeginForm("Login","Login",FormMethod.Post)) 9 { 10 @Html.ValidationSummary(true) 11 <fieldset> 12 <legend>UserInfo</legend> 13 @Html.LabelFor(m=>m.UserName) 14 @Html.TextBoxFor(m=>m.UserName) 15 16 @Html.LabelFor(m => m.Password) 17 @Html.PasswordFor(m=>m.Password) 18 19 <input type="submit" value="登陸" /> 20 </fieldset> 21 }
然后,我們需要更改我們的路由設置,把默認頁改為我們的登陸頁
1 public class RouteConfig 2 { 3 public static void RegisterRoutes(RouteCollection routes) 4 { 5 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 6 7 routes.MapRoute( 8 name: "Default", 9 url: "{controller}/{action}/{id}", 10 defaults: new { controller = "Login", action = "Index", id = UrlParameter.Optional } 11 ); 12 } 13 }
添加一個HomeController,也就是我們的主頁的控制器
1 public class HomeController : Controller 2 { 3 // 4 // GET: /Home/ 5 6 public ActionResult Index() 7 { 8 return View(); 9 } 10 11 }
代碼很簡單,就只有一個Index Action,接着添加一個Home下的Index View。
1 @{ 2 ViewBag.Title = "Index"; 3 Layout = "~/Views/Shared/_Layout.cshtml"; 4 } 5 6 <h2>HomeIndex</h2>
同樣也是很簡單的,因為,我們所要做的,僅僅是登陸成功的一個跳轉,當然,后續里面還會有別的操作。這個時候,其實就可以運行我們的程序了,但是這是沒有意義的,因為我們沒有任何的數據。所以,下面我們來給我們的程序添加數據。
初始化數據
我們可以使用EF的數據初始化策略初始化我們程序需要的數據,首先,在config文件中,有我們的連接字符串。
1 <add name="WebSiteContext" connectionString="Data Source=(localdb)\v11.0; Initial Catalog=WebSiteContext-20131204232726; Integrated Security=True; MultipleActiveResultSets=True; AttachDbFilename=|DataDirectory|WebSiteContext-20131204232726.mdf" 2 providerName="System.Data.SqlClient" />
這里我使用的是本地數據庫。大家可以根據自己的配置去完成數據庫的配置,如果是本地數據庫,會在appData中生成對應的數據庫文件。在EF中,我們可以使用Database.SetInitializer方法初始化我們的數據庫
// // 摘要: // 獲取或設置數據庫初始化策略。在從 System.Data.Entity.Infrastructure.DbCompiledModel 初始化 System.Data.Entity.DbContext // 實例時,調用數據庫初始化策略。 // // 參數: // strategy: // 策略。 // // 類型參數: // TContext: // 上下文的類型。
我們可以看到參數為IDatabaseInitializer<TContext>。EF中兩個類實現了這個接口
- DropCreateDatabaseIfModelChanges:當模型發生變化的時候才會重新創建數據庫
- DropCreateDatabaseAlways:每次重新啟動的時候都會重新創建數據庫
這里我使用的是DropCreateDatabaseIfModelChanges,泛型類型為我們的Context
1 public class WebSiteDbInitializer:DropCreateDatabaseIfModelChanges<WebSiteContext> 2 { 3 protected override void Seed(WebSiteContext context) 4 { 5 context.Users.Add(new UserInfo() { UserInfoId=0, UserName="admin", Password="admin", IsLogin=false }); 6 context.Users.Add(new UserInfo() { UserInfoId=1,UserName="edrick", Password="123", IsLogin=true}); 7 8 context.News.Add(new NewsInfo() { NewsInfoId = 0, NewsTitle = "今天霧霾", NewsContent = "今天霧霾", CreateTime = DateTime.Now }); 9 context.News.Add(new NewsInfo() { NewsInfoId = 1, NewsTitle = "今天霧霾很嚴重", NewsContent = "今天霧霾很嚴重", CreateTime = DateTime.Now }); 10 context.News.Add(new NewsInfo() { NewsInfoId = 2, NewsTitle = "今天霧霾特別嚴重", NewsContent = "今天霧霾特別嚴重", CreateTime = DateTime.Now }); 11 12 context.Products.Add(new ProductInfo() { ProductInfoId = 0, ProductName = "霧霾口罩", ProductDescription = "霧霾口罩", CreateTime = DateTime.Now }); 13 context.Products.Add(new ProductInfo() { ProductInfoId = 1, ProductName = "口罩", ProductDescription = "口罩", CreateTime = DateTime.Now }); 14 context.Products.Add(new ProductInfo() { ProductInfoId = 2, ProductName = "醫用口罩", ProductDescription = "醫用口罩", CreateTime = DateTime.Now }); 15 base.Seed(context); 16 } 17 }
重寫基類的Seed方法可以將新對象保存到數據庫中。然后我們需要在應用程序啟動的時候注冊Database.SetInitializer方法
1 public class MvcApplication : System.Web.HttpApplication 2 { 3 protected void Application_Start() 4 { 5 Database.SetInitializer(new WebSiteDbInitializer()); 6 AreaRegistration.RegisterAllAreas(); 7 8 WebApiConfig.Register(GlobalConfiguration.Configuration); 9 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 10 RouteConfig.RegisterRoutes(RouteTable.Routes); 11 BundleConfig.RegisterBundles(BundleTable.Bundles); 12 } 13 }
這樣我們就完成了數據庫的設置與數據的初始化。好了,我們可以運行我們的應用程序了。到這里,我們完成了我們的登陸。但是,前面我們說了,我們要完成的是,登陸之后可以看到新聞和產品。有了上面的基礎我們可以很快的創建新聞和產品的增刪改操作了。
我們開始創建我們的新聞Controller。跟上面的步驟一樣,基架選擇讀寫/視圖和EF。這里之所以使用基架,是因為它能幫我們生成大部分我們需要的方法和前段標簽。

接着,我們新建產品Controller

全部完成之后,我們會看到我們的新聞和產品都有了對應Controller和View。現在我們要做的就是更改這些代碼。因為前面我們說了,我們需要的只是基架幫我們生成的一部分代碼,但是它並不符合我們的要求。這里我們的要求就是我們在主頁上要能看到新聞和產品,所以我們需要把這兩個頁面的主頁合並到Home主頁里面。這里就有了一個新的問題,home主頁的數據從哪里來。從第一篇,MVC基礎中我們可以知道。我可以把數據封裝到Viewbag中,然后傳遞給View,當然,這是一種解決辦法。這里介紹另外一種解決辦法,ViewModel。
ViewModel
這里需要注意一點的事,這里的ViewModel不等於MVVM中的ViewModel。這里的ViewModel指的是我們為特定的頁面制定頁面模型。比如一般的門戶網站的首頁,我們需要顯示少量的新聞,少量的案例,少量的產品的信息。但是這些數據又都是不同的模型。所以,我們就可以給這個頁面建立一個特定於這個特面的模型。這里我們就是IndexViewModel。它包含的就是新聞和產品的集合。它給Index頁面提供數據。
1 public class IndexViewModel 2 { 3 public NewsInfo News { get; set; } 4 5 public ProductInfo Product { get;set;} 6 7 public IEnumerable<NewsInfo> NewsInfoList { get; set; } 8 9 public IEnumerable<ProductInfo> ProductInfoList { get; set; } 10 }
有了數據,我們就需要整合我們剛剛VS幫我們生成的代碼,我們需要把News Index的代碼和Product Index的代碼整合到Home Index下。但是,直接復制代碼是運行不了的,因為新聞和產品的Index綁定的模型的是IEnumerable<ModelInMVC.Models.NewsInfo>和IEnumerable<ModelInMVC.Models.ProductInfo>而我們的Home Index是沒有綁定這兩個模型。所以我們就要使用我們上面定義的ViewModel。讓IndexViewModel綁定到Index。修改HomeController的Index方法。
1 public class HomeController : Controller 2 { 3 WebSiteContext context = new WebSiteContext(); 4 // 5 // GET: /Home/ 6 7 public ActionResult Index() 8 { 9 IndexViewModel viewModel = new IndexViewModel(); 10 viewModel.NewsInfoList = context.News.ToList(); 11 viewModel.ProductInfoList = context.Products.ToList(); 12 return View(viewModel); 13 } 14 }
好了,數據准備好了,我們現在把Home Index修改為如下代碼
1 @model ModelInMVC.Models.IndexViewModel 2 @{ 3 ViewBag.Title = "Index"; 4 Layout = "~/Views/Shared/_Layout.cshtml"; 5 } 6 7 <h2>HomeIndex</h2> 8 <div> 9 <div style="width:49%;float:left;"> 10 <fieldset> 11 <legend>News</legend> 12 <p> 13 @Html.ActionLink("Create New", "Create") 14 </p> 15 <table> 16 <tr> 17 <th> 18 @Html.DisplayNameFor(model => model.News.NewsTitle) 19 </th> 20 <th> 21 @Html.DisplayNameFor(model => model.News.NewsContent) 22 </th> 23 <th> 24 @Html.DisplayNameFor(model => model.News.CreateTime) 25 </th> 26 <th></th> 27 </tr> 28 29 @foreach (var item in Model.NewsInfoList) 30 { 31 <tr> 32 <td> 33 @Html.DisplayFor(modelItem => item.NewsTitle) 34 </td> 35 <td> 36 @Html.DisplayFor(modelItem => item.NewsContent) 37 </td> 38 <td> 39 @Html.DisplayFor(modelItem => item.CreateTime) 40 </td> 41 <td> 42 @Html.ActionLink("Edit", "Edit", new { id = item.NewsInfoId }) | 43 @Html.ActionLink("Details", "Details", new { id = item.NewsInfoId }) | 44 @Html.ActionLink("Delete", "Delete", new { id = item.NewsInfoId }) 45 </td> 46 </tr> 47 } 48 </table> 49 </fieldset> 50 </div> 51 <div style="width:49%;float:right"> 52 <fieldset> 53 <legend>Product</legend> 54 <p> 55 @Html.ActionLink("Create New", "Create") 56 </p> 57 <table> 58 <tr> 59 <th> 60 @Html.DisplayNameFor(model => model.Product.ProductName) 61 </th> 62 <th> 63 @Html.DisplayNameFor(model => model.Product.ProductDescription) 64 </th> 65 <th> 66 @Html.DisplayNameFor(model => model.Product.CreateTime) 67 </th> 68 <th></th> 69 </tr> 70 71 @foreach (var item in Model.ProductInfoList) 72 { 73 <tr> 74 <td> 75 @Html.DisplayFor(modelItem => item.ProductName) 76 </td> 77 <td> 78 @Html.DisplayFor(modelItem => item.ProductDescription) 79 </td> 80 <td> 81 @Html.DisplayFor(modelItem => item.CreateTime) 82 </td> 83 <td> 84 @Html.ActionLink("Edit", "Edit", new { id = item.ProductInfoId }) | 85 @Html.ActionLink("Details", "Details", new { id = item.ProductInfoId }) | 86 @Html.ActionLink("Delete", "Delete", new { id = item.ProductInfoId }) 87 </td> 88 </tr> 89 } 90 </table> 91 </fieldset> 92 </div> 93 </div>
運行程序,我們可以看到如下的顯示

當我們點擊增加,修改,詳細,刪除的時候出現404.因為基架生成的Controller路由不符合我們修改后的路徑。所以,下一步,我們要修改頁面的鏈接和Contrller的部分代碼。
修改之后的頁面代碼
1 @model ModelInMVC.Models.IndexViewModel 2 @{ 3 ViewBag.Title = "Index"; 4 Layout = "~/Views/Shared/_Layout.cshtml"; 5 } 6 7 <h2>HomeIndex</h2> 8 <div> 9 <div style="width:49%;float:left;"> 10 <fieldset> 11 <legend>News</legend> 12 <p> 13 @Html.ActionLink("Create New", "Create","News") 14 </p> 15 <table> 16 <tr> 17 <th> 18 @Html.DisplayNameFor(model => model.News.NewsTitle) 19 </th> 20 <th> 21 @Html.DisplayNameFor(model => model.News.NewsContent) 22 </th> 23 <th> 24 @Html.DisplayNameFor(model => model.News.CreateTime) 25 </th> 26 <th></th> 27 </tr> 28 29 @foreach (var item in Model.NewsInfoList) 30 { 31 <tr> 32 <td> 33 @Html.DisplayFor(modelItem => item.NewsTitle) 34 </td> 35 <td> 36 @Html.DisplayFor(modelItem => item.NewsContent) 37 </td> 38 <td> 39 @Html.DisplayFor(modelItem => item.CreateTime) 40 </td> 41 <td> 42 @Html.ActionLink("Edit", "Edit","News",new { id = item.NewsInfoId },null) | 43 @Html.ActionLink("Details", "Details", "News", new { id = item.NewsInfoId }, null) | 44 @Html.ActionLink("Delete", "Delete", "News", new { id = item.NewsInfoId }, null) 45 </td> 46 </tr> 47 } 48 </table> 49 </fieldset> 50 </div> 51 <div style="width:49%;float:right"> 52 <fieldset> 53 <legend>Product</legend> 54 <p> 55 @Html.ActionLink("Create New", "Create", "Product") 56 </p> 57 <table> 58 <tr> 59 <th> 60 @Html.DisplayNameFor(model => model.Product.ProductName) 61 </th> 62 <th> 63 @Html.DisplayNameFor(model => model.Product.ProductDescription) 64 </th> 65 <th> 66 @Html.DisplayNameFor(model => model.Product.CreateTime) 67 </th> 68 <th></th> 69 </tr> 70 71 @foreach (var item in Model.ProductInfoList) 72 { 73 <tr> 74 <td> 75 @Html.DisplayFor(modelItem => item.ProductName) 76 </td> 77 <td> 78 @Html.DisplayFor(modelItem => item.ProductDescription) 79 </td> 80 <td> 81 @Html.DisplayFor(modelItem => item.CreateTime) 82 </td> 83 <td> 84 @Html.ActionLink("Edit", "Edit", "Product", new { id = item.ProductInfoId },null) | 85 @Html.ActionLink("Details", "Details","Product", new { id = item.ProductInfoId },null) | 86 @Html.ActionLink("Delete", "Delete", "Product",new { id = item.ProductInfoId },null) 87 </td> 88 </tr> 89 } 90 </table> 91 </fieldset> 92 </div> 93 </div>
這樣,我們頁面的鏈接都可以生效。我們看看之前的頁面鏈接@Html.ActionLink("Create New", "Create")。我們簡單的提一下這個方法,這個方法是鏈接到一個Action。如果不提供Controller名稱,就會在當前contrller中尋找Action,但是這里我們的HomeContrller中是沒有Create這個Action的,所以我們需要提供Controller名稱。@Html.ActionLink("Create New", "Create","News")。這樣我們所有的頁面都可以生效。但是這里我們還沒有修改對應的Controller。所以不保證程序能正確的執行。接下來,我們來修改Contrller,這里我們只修改NewsContrller。因為ProductController的修改方法跟NewsContrller是一樣的。
CreateNew會鏈接到NewsController的Create Action。我們在NewsController里面可以看到兩個Create方法,但是有區別的是一個Create方法標注了[HttpPost]屬性。這里HttpPost指的是,當http的請求方式為post的時候調用這個方法。沒有標注則為get方式。
1 public ActionResult Create() 2 { 3 return View(); 4 } 5 6 // 7 // POST: /News/Create 8 9 [HttpPost] 10 [ValidateAntiForgeryToken] 11 public ActionResult Create(NewsInfo newsinfo) 12 { 13 if (ModelState.IsValid) 14 { 15 db.News.Add(newsinfo); 16 db.SaveChanges(); 17 return RedirectToAction("Index"); 18 } 19 20 return View(newsinfo); 21 }
可以看到,沒有標注post的action僅僅只是返回一個view,也就是我們的編輯界面。而標注了post則是具體的提交數據庫。為post請求的Create在CreatView中被調用。也就是form的提交。修改和刪除也是跟添加是一樣的道理,這里我們只是了解即可。以后會詳細講解。代碼這里我也不列出所以,文章的最后會提供正代碼的下載。
到這里,所有的代碼就完成了。但是我們還有兩個很重要的概念沒有解釋
強類型視圖
前面我們提到,我們有兩種方式給視圖傳遞數據,一種是使用ViewBag。一種是我們上面使用的方式。我們稱之為強類型視圖。那么這兩種方式有什么區別呢?ViewBag從原理上來說其實是一個數據字典,只不過所存儲的是動態類型。那么在前段使用的時候我們是得不到數據的類型的。所以必要的時候我們需要進行類型轉換。
ViewBag.ViewModel = viewModel;
在使用的時候,我們需要進行轉換,因為我們拿不到類型信息

ViewBag.ViewModel.ProductInfoList as IEnumerable<ModelInMVC.Models.ProductInfo>這種方式我們通過轉換拿到了數據類型。這對於我們寫代碼來說是比較繁瑣的。我們也可以使用動態類型來迭代。但是又不能使用智能感應。@foreach (dynamic item in ViewBag.ViewModel)。有沒有在一種方式能夠不使用轉換,又能智能感應。那就是強類型視圖。
所謂強類型視圖,不過是指視圖與某類型進行關聯,從根本上講,就是在頁面聲明了某一個類型的對象。那么我們在頁面的任何地方都可以使用這個對象,這樣就在頁面里面建立了強類型的對象。也稱之為強類型視圖。對象的實例化是在返回View的時候進行的return View(viewModel);這樣就完成了對象在頁面的聲明,賦值。之后我們就可以使用了。
模型綁定
在ASP.NET時代,我們如果要獲取http請求的參數或者窗體提交的值需要使用QueryString或者Request.Form[“”]這樣的方式來獲取值,這種編碼方式是很乏味的,在MVC中模型綁定機制幫我們解決了這種乏味的編碼方式。我們之前一直在說約定優先於配置。模型綁定又是一個約定優先於配置的例子。如果有一種方式讓程序幫我們自動從url或者form中取得數據,並且自動幫我們構建對象。那么那個乏味的過程就被取代了。怎么樣才能讓程序幫我們構建對象呢?可不可以,我們遵循一種約定,那么程序就會依照這種約定幫我們構建對象。這種約定就是命名約定,我們不管是get請求或者post請求。只要按照一種約定去命名我們的參數,命名我們的數據。那么就是可能的。
- 簡單類型請求:參數的名稱與controller中action參數的名稱一致,那么模型綁定機制就會幫我們自動解析數據
- 復雜類型的請求:在表單中,我們可以給輸入元素命名,名稱可以與屬性名稱相同。模型綁定機制就會根據表單的key和value幫我們構建對象。

如果不是復雜類型,原理其實也是一樣的。這里模型綁定組件會在請求中查找數據,這里的請求可以是路由數據,查詢字符串和表單集合。
比如public ActionResult Details(int id = 0)這個方法,模型綁定組件,會在請求中尋找name為id的參數,然后傳遞給方法。如果在操作是有參數的情況下,模型綁定會隱式的工作。但是我們也可以顯式的調用模型綁定
NewsInfo newsinfo = new NewsInfo(); TryUpdateModel(newsinfo);
這時,模型綁定會顯式的工作,會從請求中查找並且構建對象。模型綁定的副產品就是模型狀態。模型綁定器構建的模型的每一個值都會在模型狀態中有一條相應的記錄,來查看模型綁定是否成功。這里涉及到驗證機制,也就是說,如果完成了所有的驗證,那么模型驗證就會通過,如果某個值完成不了驗證。那么模型綁定就會失敗。這也是對提交的值的驗證。ModelState.IsValid驗證模型狀態。如果驗證失敗,那么模型狀態將包含導致綁定失敗的屬性名,嘗試的值以及錯誤消息。下一篇,我會詳細的講解模型狀態和MVC中的驗證。
