什么是Model
Model可翻译为“模型”,笔者认为译成“数据模型”会更贴切一些,因为Model负责所有与“数据”相关的任务,大致如下:
定义数据结构
负责与数据库沟通
从数据库中读取数据
将数据写入数据库
运行运储程序
数据格式验证
定义与验证商业逻辑视图
对数据进行各种加工处理。例如:指定特定实体(Entity)某些字段的默认值。
以.NET或Java平台开发经验来说,你可以想象Model是一个命名空间(Namespace或Package),定义了一堆Type或Class来负责所有跟数据相关的工作。常见的相关技术包括ADO.NET,强类别数据集(Typed DataSet)、Entity Framework、LINQ to SQL、LINQ to SQL partial method、数据访问层(Data Access Layer)、Repository Pattern。
什么是控制器
Controller可翻译为“控制器”,顾名思义是“掌控全局的对象”,其负责的工作如下:
决定与“用户”沟通的管道,以ASP.NET MVC为例就是HTTP或HTTPS.
决定系统运作的“流程”。例如,从Controller接收到数据后要立刻转向
(Redirect)到另一个页面。
负责从Model取得数据,我们可以在Cotroller的类别中利用Model提供的类别来取得数据。
决定应该显示哪个View,一个网站里有很多呈现的View,要挑选哪一个View来呈现给用户,是Controller的责任。或是当Controller运行的过程中发生异常时,也可由Cotroller挑选适当的View进行响应。
什么是View?
View负责呈现在用户面前的东西,最简单的说法就是输出与输入,输出工作就是呈现在浏览器上的界面,例如:输出HTML、XML,等等。输入工作则是将用户输入的数据传回服务器,例如,在浏览器上呈现网页窗体让用户输入。
以下,简单地介绍在View中与输出/输入有关的工作。
输出
从Controller取得数据,并显示在用户界面上。
决定要用什么技术来呈现“用户界面”(例如,HTML,XML,Silverlight,flash,等)
负责界面的排版、字型、颜色、美观与各种呈现方式。
将Controller传送的数据显示于界面,而数据是参考自Model的定义。
参考Model的数据格式定义数据显示。
输入
负责将数据送回Controller
HTML窗体通过GET或POST输出数据。
决定数据应该送到哪一个Controller的Action中。
决定数据传送的方式,例如:GET、POST、XML HTTP Request(XHR)。
前端基本的数据格式验证。
验证功能,例如,使用JavaScript验证表单域是否输入。
参考Model的类别定义,在Visual Studio中利用Intellisense撰写程序。
1.利用asp。Net mvc 4项目模板创建项目
开启visual studio2012,选择“文件”->“项目”菜单命令。
在打开的“新建项目”对话框中展开Web->“ASP.NET MVC4 Web应用程序”,在“名称”文本框中输入“MvcGuestbook”,如图1所示
图1
新增项目时,会先弹出项目模板选择精灵,询问你要使用哪个项目模板。在此我们选择“Internet应用程序”,而其他选项保留其默认即可,最后单击“确定”按钮,如图2所示
图2
运行“调试”->“启动调试”(或按下F5键)命令运行网站,即可启动一个默认的ASP.NET MVC4网站。
此网站具有非常基本的功能,如图3所示,包括3页简单的页面与会员机制,这些页面都套用主板页面(Layout Page),使用ASP.NET内建的Membership功能,可以进行会员注册,登录,注销等。
图3 运行默认的ASP.NET MVC4网站
在ASP.NET MVC4项目新增完成后,会自动创建几个标准的目录结构与重要文档。如图4所示是默认项目模板所创建的重要文档。
图4
其文档解释为:其中app_start中BundleConfig.cs定义css/JS打包的规则,FilterConfig.cs为全局Action Filter定义的地方,RouteConfig为定义路由(Routing)的地方。
Content放置网站内所有静态属性,例如:图片,CSS,下载文件,影片...
Controllers防止ASP.NET MVC控制器。
Models放置所有ASP.NET MVC与Models有关的程序代码,例如,EDMX,DBML...
Scripts放置所有JavaScript,VBScript脚本文件存放地址
Views放置ASP.NET MVC检视
Shared放置全站共享检视的目录,可能防止Layout页面或Partial View或自定义默认错误页面等。
ASP.NET MVC若要通过“网址路径”来查找文档,就必须配合ASP.NET MVC架构的规范来查找文档,事实上ASP.NET MVC的“网址路径”与“文档路径”的对应关系是通过“网址路由”来定义的,我们可以从项目内的APP_Start\RouteConfig.cs文档看到一个RegisterRoutes方法,定义如下:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
这里定义了两个默认的网址路由(Routing)。
IgnoreRoute:设置*.asd等格式的网址路径不要通过ASP.NET MVC运行。例如:ASP.NET内建的Trace.axd或其他默认的HttpHandler都不要通过ASP.NET MVC处理,这样设置的目的是让ASP.NET MVC与ASP.NET Web Form可以在同一平台下运行而不会互相影响。
MapRoute:通过MaoRoute方法是定义ASP.NET MVC网址路由最主要的方式。这一条路由定义了三个参数。
Name:路由名称。
Url:设置网址路径如何对应到控制器、动作与路由值。
Default:设置{controller}.{action}.{id}这3个路由参数的默认值。
从几个默认的MapRoute可以得知,当在浏览器中输入http://locallhost/Home/About时,在Routing的对应之下,由于网址路径的部分为Home/About,所有会对应出{controller}为Home,而{action}为About,因此ASP.NET MVC就会先进入Controllers目录查找Home控制器(也就是HomeController.cs文档),然后再查找该控制器内的About公开方法(Public Method),这个公开方法就是MVC的动作(Action),也就是实际运行网页程序的入口点。
当在浏览器中输入http://locallhost/想要取得首页时,在Routing的对应之下,由于网站路径的部分没有任何属性,所以使用MapRoute的第三个参数(defaults)所设置的默认值来替代,因此网站首页的网址就会先进入Controllers目录查找Home控制器,然后再查找Index这个公开方法,来进一步运行ASP.NET MVC的所有过程。
我们先来看一下默认的HomeController的属性:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcGuestbook.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "修改此模板以快速启动你的 ASP.NET MVC 应用程序。";
return View();
}
public ActionResult About()
{
ViewBag.Message = "你的应用程序说明页。";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "你的联系方式页。";
return View();
}
}
}
首先,控制器(Controllers)类别在开发的时候必须符合以下规范:
类别名称一定要由Controller结尾。例如:GuestbookController就代表Guestbook控制器。
类别继承于Controller基类
类别中须包含数个回传值为ActionResult的公开方法,这些方法在ASP.NET MVC中成为动作(Action)。
在默认首页Indexx动作中,第一行的ViewBag是一个动态(dynamic)类型的对象,因此该对象可以设置任意类型的数据进去,这里所指定的属性与值都可以在ASP.NET MVC的View中读取。
ViewBag.Message=”修改此模板以快速启动你的ASP.NET MVC应用程序。”;
第二行的return View();事实上是来自于Controller基类的一个辅助方法(Helper Method),它会回传一个ViewResult对象。ViewResult是继承自Action'Result类,主要用途是告知ASP.NET MVC框架我要响应一个视图(View),而该视图就来自于ASP.NET MVC框架所设置的“默认路径”,以Home控制器与此Index动作为例,通过View()辅助方法就会去告知ASP.NET MVC框架:“我要显示Views/Home/Index.cshtmlz这张网页的运行结果”。
接着,我们用Visual Studio的新增功能切换至该激活的视图,先将光标移至动作方法的定义处,然后单击鼠标右键,在弹出的快捷菜单中选择“转到视图”命令,从该视图的页面属性可以发现,页面中一开始就是@开头的语法,如图5所示,这个语法是从ASP.NET
MVC 3开始新增的Razor语法,跟以往我们撰写ASP.NET MVC2.0或ASP.NET Web Form的ASPX差异甚大,但新版的Razor语法让套用程序的View页面变得非常干净。
图5 该动作(Action)相对应的视图(View)
不过当你仔细一看,你会发现这个Index.cshtml页面并不是一个完整的HTML页面,以往我们在ASP.NET Web Form或ASP.NET MVC 2.0中会套用MasterPage属性来套用主版面,但ASP.NET MVC4.0的主版面到哪去了呢?
这时我们先通过解决方案资源管理器浏览到项目的Views目录,该目录里面有个_ViewStart.cshtml文档,如图6所示,这个文档会在所有View运行之前先装入,通常我们会在这个文档里设置View的一些基本属性,例如,要装入的主版页面(Layout Page)。
图6 浏览至解决方案管理器中的_ViewStart.cshtml文档
打开_ViewStart.cshtml文档,其中定义了一个Layout属性,并指向到~/Views/Shared/_Layout.cshtml主版页面,这也代表了在Views目录下所有的视图(View)都会默认装入该主版页面,如图7所示
图7 _ViewStart.cshtml文档的属性
打开~/Views/Shared/_Layout.cshtml主版页面后,会发现这里含有的完整的HTML结构,如图8所示
图8 ~/Views/Shared/_Layout.cshtml文档的属性
我们刚刚在Controller中看到ViewBag.Message被设置了一个字符串,到了Index.cshtml视图(View)
就可以通过以下语法将其信息读出,并显示于网页属性中,如图9所示
@ViewBag.Message
图9 Views\Home\Index.cshtml文档的属性
另外,在Index.cshtml页最上方也设置了一组ViewBag.Title属性,这里所定义的属性值也会自动被传入同一个View以及默认的_Layout.cshtml的主版页面里,如图10所示
图10 Views\Shared\_Layout.cshtml文档的属性
2创建模型
创建模型的操作页面如下:
第一步:在“解决方案资源管理器”窗口中选择Models目录,单击鼠标右键,在弹出的快捷菜单中选择“添加”->“类”命令。
第二步:我们将类取名为Guestbook.cs,并单击“添加”按钮,如图11所示
图11 设置类名称
第三步:新增一个简单类,定义出一个留言板所需的数据模型,其程序代码如下:
namespace MvcGuestbook.Models
{
public class Guestbook
{
public int Id { get; set; }
public string 姓名 { get; set; }
public string Email { get; set; }
public string 内容 { get; set; }
}
}
第四步:s生成一次解决方案,并确认没有任何问题。
3.创建控制器、动作和视图
步骤如下:
第一步:在“解决方案资源管理器”窗口中选择Controller目录,单击鼠标右键,在弹出的快捷菜单中选择“添加”->“控制器”命令
第二步:在“添加控制器”对话框的“控制器名称”文本框中输入控制器名称“GuestController”。
另外,在基架(Scaffold)选项中还有4个选项可设置,这些是Visual Studio2012提供的代码生成器模板设置值,只要妥善设置这些参数,就能利用Visual Studio2012开发工具快速帮助我们生成Controller程序代码。不仅如此,在Visual Studio2012内建的AS.NET MVC4项目模板里,甚至可以在创建Controller的同时直接将View也创建完成。
在这里,我们在“模板”下拉列表框中选择“包含读/写操作和视图的MVC控制器(使用Entity Framework)”选项,而在“模型类”下拉列表框中选择 步骤1新增的Guestbook模型类别,如图12所示
图12 为控制器命名并设置基架选项
第三步:在“添加控制器”对话框的“基架选项”中还有个数据“上下文类”下拉列表框,由于我们尚未创建“数据上下类”,但我们可以借此通过这个新增项目精灵帮我们自动创建,因此在这里我们可以选择“<新建数据上下文...>”选项。
第四步:选择之后默认会帮你填上“项目名称”+Context作为类别的名称,你可以更改它,或者直接沿用默认值也可以。在这里我们直接以默认的命名继续,单击“确认”按钮完成,如图13所示。
图13 新数据上下文
第五步:单击“添加”按钮完成,如图14所示
图14 单击“添加”按钮新增控制器
此时,Visual Studio2012会新增一个新的Controller类别档,名为GuestbookController.cs,在Controller目录下。此外,由于在添加控制器时我们选用“包含读/写操作和视图的MVC控制器(使用Entity Framework)”模板,所以除了新增控制器之外,它连同所有视图页面(View Page)也全部一次创建完成。而我们在添加控制器的过程中新增了一个数据上下文类(DataContextClass),因此在Model目录下多一个MvCGuestbookController.cs,如图15所示
图15 通过Visual studuo2012自动新增的控制器,视图页面与信息内容类
4.测试当前创建好的留言板网页
运行“调试”->”启动调试”命令将网站运行起来。网页运行起来后,必须更改URL进入我们刚刚新增的Guestbook控制器来查看页面。以图16所示,打开网页的网址是http://locallhost:7271/,我们在网址路径部分加上Guestbook,网址会变成http://locallhost7271/Guestbook,最后按Enter键进入该页面。
图16 更改网址以进入Guestbook页面
进入该页面后,你会看到一些基本页面与Guestbook相关字段。这是一个列表页面,但还没有任何信息,所以当前还看不到任何其他属性。这个页面中还有一个Create New链接,这是进入“创建留言”页面的链接,直接单击进入,如图17所示
图17 显示Guestbook的默认页面
进入到Create New页面后,会看到有个默认的窗体,有三个字段分别跟我们之前创建的Guestbook模型类的属性一样。我们直接在这三个字段上输入相对应的数据,然后单击Create按钮输出窗体,如图18所示
图18 Guestbook的Create页面
接着会回到Index页面,此时你就会发现数据已经写入数据库了!如图19所示
图19 回到Guestbook的默认Index页面
然后,我们再来测试Edit功能,编辑刚刚输入的留言,看到的界面和刚刚Create的页面类似,但是刚输入的数据会自动带入。我们更改“姓名”字段上的文字,如图20为例,在姓名后面加上“(TEST)”字样,最后单击Save按钮保存。
图20 显示Guestbook的默认Edit页面
此时你会看到该数据“姓名”字段已经被更新成功,如图21所示
图21 回到Guestbook的默认Index页面
最后我们再测试Delete功能。这里会先显示当前数据库中的数据,如图22所示
图22 显示Guestbook的默认Delete页面
5.查看数据库属性
当确定数据被写入成功,那我们怎么能知道数据保存到哪里去?如果你完全按照本书步骤创建出第一个项目,那么你应该会在项目的App_Data目录下发现一个隐藏的数据库文档,为了在解决方案资源管理器中看到这些不在项目范围内的文档,必须先单击解决方案资源管理器的“显示所有文件”按钮,再展开App_Data文件夹,即可坎肩相关文档,如图
23所示
图23 显示所有文件
6.了解自动生成的程序代码
刚刚创建的那些完整功能的程序代码都是Visual Studio2012的ASP.NET MVC4项目模板帮我们自动创建的,这样的话我们学不到知识。接下来,我们将逐步了解这些通过工具帮我们生成的程序代码。
1.了解列表页面的Index动作
我们先打开Controller目录下的GuestController.cs来看看,在GuestbookController类内的第一行定义了一个名为db的类别私有变量,其类别为MvcGuestbookContext,也就是我们的数据内容类,在这整份Controller类别中都将会用到db这个变量对数据库进行访问,如图24所示。
图24 GuestbookController类别中的MvcGuestbookContext类别对象
图24所示Index这个公开方法(public method)的程序代码很简单,只有一行,其中View()是来自Controller基类的一个辅助方法(Helper Method),该辅助方法有8个多载,其中第3个多载是闯入一个model参数,此参数的对象数据将会传递给View使用。
我们在这里传入的是db.Guestbooks.ToList(),也代表着把所有Guestbooks回传的所有数据全部传入View中,让View里的程序去使用。接着如图25所示,切换至Index动作方法(Action Method)所对应的视图页面(View Page)。将光标移至return View(db.Guestbooks.ToList()),点击右键,单击转到视图。
2.了解列表页面的Index视图
在Views\Guestbook\Index.cshtml视图页面的第一行,便是一个@model的声明,后面接着一个类别,而该类别是一个Guestbook的集合对象(IEnumerable),所代表的是这个View将会以这个用@model声明的类被为“主要模型”,在View里面的程序代码也将会参考到该类别来使用。
接下来的这段ViewBag.Title的设置在本页里并没有用到,而是要传给主版页面(Layout Page)用的,将会显示在HTML的<title>卷标里。
接着我们看到以下这段,是用来创建一个ASP.NET MVC的链接,链接显示名称为“Create New”,而该链接将会链接到当前这也的控制器的Create动作(Action),至于超链接的输出则由ASP.NET MVC负责。
由于这是列表页面,所以会有一个<table>标签,代表着要将数据输出成表格,而表格自然会有字段标题,因此会看到以下网页程序片段:
这里的@Html.DisplayNameFor辅助方法的主要用途是输出特定字段的显示名称,传入的参数则是以一个Lambda Expression表示法,该表示法里的model变量代表的正是我们在View里第一页设置的@model类别,所以我们可以在挑选字段时,就利用Visual Studio2012的Intellisense来帮助我们进行选择。
使用@Html.DisplayNameFor默认会直接输出属性名称,所以上述程序最后输出的HTML如下:
如果要输出的显示名称和属性名称不同,则必须更改Guestbook模型类别的定义,特定属性(Property)上加上一个System.ComponentModel命名空间下的DisplayName属性(Attribute),在这里我们将Email字段显示名称更改为“电子邮件地址”,如图26所示
图26 在Model里加上DisplayName属性(Attribute)
修改完Model的定义后,View里的程序完全不用改,按F5运行网站,Email字段所输出的域名就会直接变成“电子邮件地址”,如下:
在View里的最后一段Code是一个foreach循环,且数据来自于Model对象,这里的Model对象是每个View都有的属性,代表的就是从Controller传过来的数据,而这个Model对象本身是一个泛型类别,也就是说,这个Model对象的了类别会完全等同于你在View最上方用@model声明的那个类别。
如下程序代码所示,我们通过循环去除Model里的每条数据,而每条数据的类别正好是MvcGuesrbook.Models.Guestbook模型类别。
D模型的数据。
3.了解创建信息窗体的Create动作
接着我们来看创建信息功能的动作(Action),切换回GuestbookController类别里,看一下在这个控制器里有两个同名的Creeate方法,从批注这里可以发现,第一个是给HTTP GET方法用的,另一个是给HTTP POST方法用的。
在这里值得一提的是,在第二个Create方法有特别套用一个HttpPost属性(Attribute),该属性告知ASP.NET MVC框架此动作(Action)只会接受HTTP POST过来的信息,这个属性又有一个专有名词称为动作过滤器(Action Filter)或动作选择器(Action Selector)。
//
// GET: /Guestbook/
public ActionResult Index()
{
return View(db.Guestbooks.ToList());
}
//
// GET: /Guestbook/Details/5
public ActionResult Details(int id = 0)
{
Guestbook guestbook = db.Guestbooks.Find(id);
if (guestbook == null)
{
return HttpNotFound();
}
return View(guestbook);
}
//
// GET: /Guestbook/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Guestbook/Create
[HttpPost]
public ActionResult Create(Guestbook guestbook)
{
if (ModelState.IsValid)
{
db.Guestbooks.Add(guestbook);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(guestbook);
}
//
首先,我们进入http://localhost:10752/Guestbook/Create页面时,此时的HTTP要求方法一定是GET方法,因此第一个Create()动作会先被ASP.NET MVC选中来运行,并显示默认的Create视图页面(View Page)。
我们现在切换至Create的视图页面。
4了解创建信息窗体的Create视图
在Create.cshtml里面跟刚刚的Index.cshtml一样,一开始就是先来个@model声明,声明此页面以MvcGuestbook.Models.Guestbook为主要模型。
接着则出现一个ASP.NET MVC的窗体声明,与窗体内的HTML声明,代码如下:
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Guestbook</legend>
<div class="editor-label">
@Html.LabelFor(model => model.姓名)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.姓名)
@Html.ValidationMessageFor(model => model.姓名)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Email)
@Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.内容)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.内容)
@Html.ValidationMessageFor(model => model.内容)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
这里使用的是Html.BeginForm()辅助方法,该辅助方法将会输出<form>标签,而且必须以using包起来,如此便可在using程序代码最后退出时,让ASP.NET MVC帮你补上</form>标签。以本页为例,最后窗体输出的HTML结构如下:
<form action=”/Guestbook/Create” method=”post”>
...
</form>
接下来的这段是用来显示当表单域发生验证失败时,显示的错误信息:
@Html.ValidationSummary(true)
在这个创建信息的窗体里一共有三个字段,你会发现这三个字段的定义都差不多,如下代码段所示:
<div class="editor-label">
@Html.LabelFor(model => model.姓名)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.姓名)
@Html.ValidationMessageFor(model => model.姓名)
</div>
<div class="editor-label">
这里的@Html,LablleFor用来显示特定字段的显示名称。而刚刚也介绍过一个@HTML
Razor语法 | HTML输出结果 |
@Html.DisplayNameFor(model=>model.Email) | 电子邮件地址 |
@Html.LabelFor(model=>model.Email) | <label for=”Email”电子邮件地址</label> |
在ASP.NET MVC 里主要使用@Html.EditorFor来输出表单域,以此字段为例,输出的HTML代码段如下,这里出现的class属性是默认输出的,你可以借助此设计一些CSS样式来改变该输出字段的样式。:
<input class=”text-box single-line” name=”姓名” type=“text” value=””/>
最后一个@Html.ValidationMessageFor是用来显示字段验证的错误信息,不过,当前为止,在这个Create页面里,我们并没有做出任何字段验证的设置。
在ASP.NET MVC里,窗体验证可以用一种极其简单的方式进行设置,而且只要在Model里进行定义即可同时解决客户端与服务器端验证的工作。我们再次开启Guestbook模型类别,并在需要必填的属性加上一个System.ComponentModel.DataAnnotations命名空间下的Required属性(Attribute),如图27所示
图27 在Model里加上Required必填属性(Attribute)
修改完Model定义后,跟刚刚一样,View里的程序完全不用更改,按F5键运行网站。不过这次运行出现了一个Entity Framework Code First的运行时期错误,其错误如图28所示
解决这个问题最简单的方法就是好将数据库整个砍掉重建,由于我们现在还处于练习ASP.NET mvc快速上手的截断,所以请参照一下步骤设置。我们由Entity Framework帮助我们运行重建数据库的动作。不过请特别注意,这里仅为了教学方便才这样设置,在生产环境下千万不要启动一下参数,否则将数据库砍掉后,所有已经输入数据库的数据都将会消失。
还记得我们在创建控制器时曾经创建过一个MvcGuestbookContext.cs文档在Models目录下,打开此文档后,会发现有段批注帮助我们如何如何让Entity Framework自动卸载再重新生成数据库的方式,其中还包括可以让你直接复制、粘贴的演示程序代码: