上一篇我讲了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="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="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="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="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中的验证。