第1篇 体验篇
第1章 初识ASP.NET3.5MVC开发
在ASP.NET 3.5 MVC框架下开发Web应用程序,与传统的Web Forms技术不同,它需要分别开发相关的模型、控制器和视图,还需要理解URL路由的基本概念和使用方法。
本章实现了在ASP.NET 3.5 MVC框架下,NorthWind数据库中Categories数据表内容的显示、修改、添加及详细页面,以便初学者认识ASP.NET 3.5 MVC的开发流程。说明了如何构建模型、如何实现控制器及如何创建对应的视图,还特别讲解了URL路由的简单知识和应用。
本章要点:
● ASP.NET 3.5 MVC概述
● 初创ASP.NET 3.5 MVC应用项目
● URL路由
1.1 ASP.NET 3.5 MVC概述
1.1.1 为什么使用ASP.NET 3.5 MVC框架
1.Web Forms开发难以测试
在传统的ASP.NET应用开发中,微软的开发团队为开发者设计了一个较为完整、基于Web Forms的开发环境,使得构建Web应用相对容易,开发人员只需在一个可视化设计器中拖放控件,然后在表单中设置相关属性即可;开发人员通过编写代码来响应事件,使得对于程序逻辑的操作也变得非常直观。
然而在Web Forms中,微软构建了一个非常复杂的引擎,从而给页面的执行过程带来了许多的负面效应。开发者很难了解这背后的HTML是如何运作的,由于编程代码与HTML语言共处于同一页面,所以对于页面设计人员来说非常不友好;同时,如果没有合理控制ViewState的话,很容易得到一个包含大量ViewState的页面,使得该页面的尺寸远远超过所需的内容,最终页面打开的速度异常缓慢;随着Web应用越来越复杂化,不容易测试也越来越成为实际应用开发中的一个棘手问题。
2.ASP..NET 3.5 MVC开发易于测试
微软的开发团队于2007年12月发布的第一个ASP.NET 3.5 MVC预览版本以来,分别发布了8个后续的测试版本,终于在2009年3月18日正式发布ASP.NET 3.5 MVC 1.0版本。新的ASP.NET 3.5 MVC框架,避免了很多Web Forms所带来的复杂性,没有数据回传,没有在页面中保存视图状态,开发者可以完全掌控页面的呈现全过程,使用模型、视图及控制器将Web应用划分到不同的组件中,有利于开发人员与设计人员的分工,提高开发效率,同时也提高了程序的可维护性和扩展性,特别是有利于Web应用程序的测试,可以比较容易地实施测试驱动开发。
3.两种Web开发技术并存
需要说明的是,ASP.NET 3.5 MVC框架只是给开发者提供了开发Web应用程序的一种选择,而绝不是替代传统的Web Forms技术,这两种技术在不同的应用场景中,具有不同的优、缺点,开发者需要根据自己的实际情况,选择对应的技术,甚至在同一个项目中混合使用这两种技术。
ASP.NET 3.5 MVC框架与Web Forms技术的架构图如图1-1所示。
图1-1 ASP.NET 3.5 MVC框架与Web Forms技术的架构图
从图1-1中可以看出,ASP.NET 3.5 MVC框架与Web Forms技术是建立在ASP.NET 3.5基础上的两种平行技术,是微软今后同时发展的两种Web开发技术,需要支持的.NET框架为3.5版本,并且还需要安装SP1更新。
1.1.2 基本概念
MVC(Model View Controller)模式是一种较为广泛应用的结构设计模式,MVC设计模式将一般的应用程序根据功能的不同,划分为3个主要部分,它们分别是模型、视图及控制器。
ASP.NET 3.5 MVC框架基于MVC设计模式,并提供非常方便的测试功能,开发者利用ASP.NET 3.5 MVC框架,借助ASP.NET所提供的母版页及成员管理等技术,可以开发扩展性高、测试容易的Web应用程序,是今后ASP.NET应用的另外一个主要方向。
1.模型、视图、控制器
所谓模型,就是在MVC设计模式中需要被显示的数据。在通常情况下,该模型需要从数据库中读取数据、保存模型的状态等,提供数据的访问方法及数据的维护。
例如,对于SQL Server中数据库NorthWind的表Products来说,一个Product对象就是一个模型,该对象需要读取数据库中的信息,并对该数据表进行查询、添加、修改等操作。对于一个比较小型的应用程序而言,模型也许只是概念上的,假如一个应用程序需要读取数据,然后显示在用户界面上,而在该应用程序中并不存在一个物理上的数据模型或者相关的类,那么此时被读取的数据就是模型。
所谓视图,就是用来显示模型中数据的用户界面。对于数据表Products来说,在一个界面中显示该数据表的详细信息,该界面就是数据表Products的一个视图,一般来说,视图就是HTML页面。
所谓控制器,就是用来处理对用户的输入或者交互命令,以便改变模型的状态,选择适当的视图来显示对应模型的数据。
2.MVC之间的相互关系
图1-2说明了ASP.NET 3.5 MVC中模型、视图及控制器之间的相互关系。
图1-2 ASP.NET 3.5 MVC各组件间的关系
从图1-2中可以看出,当用户在浏览器中输入浏览地址,到获得页面的反馈结果,一般需要经过以下5个步骤。
(1)当用户在浏览器中输入浏览地址,发出页面的请求时,实际上就是向控制器发出相关的命令。
(2)控制器接收用户的请求命令之后,向模型请求获得相关的数据。
(3)模型将对应的数据返回给控制器。
(4)控制器将有关数据发送到指定的视图。
(5)指定的视图呈现被指定的数据。
从上述的5个步骤中可以知道,控制器在其中扮演着非常重要的角色,控制器不仅处理用户的请求,还实现与模型之间的交互,对指定的视图发送相关的命令,在实际的ASP.NET 3.5 MVC应用开发中,开发者的主要工作就是实现控制器的编码。
1.1.3 ASP.NET 3.5 MVC框架的特点
1.易于单元测试
在ASP.NET 3.5 MVC框架中,通过模型、视图和控制器,很好地分离了用户输入逻辑、业务逻辑和界面显示逻辑,因此非常容易实现Web应用程序的单元测试,开发者还可以使用任何与.NET框架兼容的其他测试方法,在ASP.NET 3.5 MVC框架的源码中,包括大量的单元测试代码,可供开发者学习和借鉴。
2.容易实施测试驱动开发
开发者可以使用ASP.NET 3.5 MVC框架实施测试驱动开发,事实上,ASP.NET 3.5 MVC框架本身在开发过程中就是采用的测试驱动开发的。
3.可扩展、可替换
ASP.NET 3.5 MVC框架是可扩展的、可被替换的。ASP.NET 3.5 MVC框架中的组件可以被替换或者个性化,例如可以使用其他的视图引擎、URL路由策略等。
4.支持Web Forms中的有关特性
在ASP.NET 3.5 MVC框架中,强大的URL映射组件使得开发者可以开发极其广泛并且可搜索URL的应用程序;在视图模板中支持各种当前的Web Forms页面(.aspx)、用户控件(.ascx)及母版页(.master),还可以使用嵌套母版页、内联表达式、数据绑定、本地化等。
在ASP.NET 3.5 MVC框架中,还支持现有的Web Forms特性,如基于窗体和基于Windows的成员、角色管理、数据缓存等。
5.URL被映射到控制器
在传统的ASP.NET应用程序中,URL通常被映射为保存在磁盘上的一个文件(例如.aspx文件),而在ASP.NET 3.5 MVC应用程序中,URL不再被映射为一个文件,URL首先被映射到一个控制器类中,该控制器处理用户的输入,选择适当的模型,获得相关数据,然后调用视图组件显示指定的数据,并返回到用户界面。
1.2 初创ASP.NET 3.5 MVC应用项目
在Visual Studio 2008中,选择“文件”→“新建”→“项目”命令,打开如图1-3所示的“新建项目”对话框。
图1-3 “新建项目”对话框
在“新建项目”对话框中,选择项目模板“ASP.NET MVC Web Application”,设置项目的名称为“MvcApplication1”,然后单击“确定”按钮,此时ASP.NET 3.5 MVC框架就会弹出一个“Create Unit Test Project”对话框,如图1-4所示。
图1-4 “Greate Unit Test Project”对话框
在图1-4中,选择“No, do not create a unit test project”单选按钮,表明不创建单元测试项目,然后单击“OK”按钮,ASP.NET 3.5 MVC框架就会创建一个基本的ASP.NET 3.5 MVC应用项目,如图1-5所示。
图1-5 ASP.NET 3.5 MVC应用项目
运行上述MvcApplication1网站,打开如图1-6所示的启动界面。
图1-6 启动界面
在图1-6中,如果单击导航菜单中的“About”链接,就会打开如图1-7所示的About界面。
图1-7 About界面
1.2.1 约定的目录结构
通过项目模板“ASP.NET MVC Web Application”创建MvcApplication1网站时,根据ASP.NET 3.5 MVC框架的约定,MvcApplication1网站将模型、视图和控制器组件及其他内容分别安放在不同的项目目录中,以便开发者维护与管理,MvcApplication1网站的目录结构如图1-8所示。
图1-8 ASP.NET 3.5 MVC应用项目目录结构
从图1-8中可以看出,数据库文件仍然存放在App_Data文件夹中;Content文件夹则存放静态文件,如样式文件、图片等;Scripts文件夹则存放JavaScript文件。
1.Models文件夹
模型组件一般存放在Models文件夹中,例如LINQ to SQL类或者ADO.NET Entity Data Model就可以存放在该目录中,该目录还可以存放有关数据访问操作的一些类、对象的定义等。
2.Views文件夹
视图组件一般存放在Views文件夹中,可以存放的文件类型包括.aspx页面、.ascx控件及.master母版页等。这里需要说明的是,对于每一个控制器,在Views文件夹中都有一个与控制器名称相对应的目录。例如,存在一个控制器HomeController,那么在Views文件夹中,就必须创建一个Home(控制器HomeController名称的前面部分)的目录,这样当ASP.NET 3.5 MVC框架通过控制器HomeController加载相关的视图时,就会自动寻找Views/Home目录下的相关.aspx页面。
3.Shared文件夹
对于视图组件中的公用部分,可以创建一个名称为“Shared”的文件夹,该目录不属于单个的控制器,而是属于所有的控制器,在Shared中可以存放母版页、CSS样式表等文件。
4.Controllers文件夹
控制器组件一般存放在Controllers文件夹中,控制器的命名约定采用XXXController的方式。
另外需要说明的是,在ASP.NET 3.5 MVC框架中,使用了Global.asax文件中的后置代码文件Global.asax.cs,并在该文件的Application_Start()方法中设置了URL路由,以及相关的路由逻辑。
打开Global.asax.cs文件,Application_Start()方法中的实现代码,见代码清单1-1。
代码清单1-1 Application_Start()方法中的实现代码
1: protected void Application_Start() 2: { 3: RegisterRoutes(RouteTable.Routes); 4: } 5: 6: public static void RegisterRoutes(RouteCollection routes) 7: { 8: routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 9: 10: routes.MapRoute( "Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" } ); 11: }
在上述代码中,定义了两个URL路由,第8行定义了可以忽略的路由配置,也就是说,不需要路由处理程序去处理这些路由,而第10行则设置了一个默认的路由。
在ASP.NET 3.5 MVC框架中,还需要通过配置文件Web.config注册专门的HTTP模块,在httpModules节中,注册了UrlRoutingModule类,用于解析URL的路由,这是使用ASP.NET 3.5 MVC框架或者传统的ASP.NET程序的根本区别。
UrlRoutingModule模块注册的实现代码,见代码清单1-2。
代码清单1-2 UrlRoutingModule模块注册的实现代码
1: <httpModules> 2: <add name="UrlRoutingModule" type="System.Web.Routing. UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> 3: </httpModules>
从上述代码中可以看出,第2行添加了一个名称为UrlRoutingModule的HTTP模块,正是注册了该模块,ASP.NET程序就会使用ASP.NET 3.5 MVC框架,将页面的请求转换为URL路由,并调用相关控制器中的相关方法,实现指定视图的输出。
1.2.2 执行过程
1.UrlRoutingModule模块入口
当执行基于ASP.NET 3.5 MVC框架的MvcApplication1网站时,根据浏览器中的URL地址,该URL地址首先被传递到上述设置的UrlRoutingModule模块,该模块解析该URL地址,然后选择相关的URL路由,并得到对应的IHttpContext对象来处理该URL路由。在默认情况下,该IHttpContext对象就是MvcHandler对象。通过MvcHandler对象,选择相关的控制器来处理用户的请求。
因此,UrlRoutingModule模块和MvcHandler对象是基于ASP.NET 3.5 MVC框架网站运行的入口点,主要实现以下3项功能:
● 选择适当的控制器。
● 获得指定控制器的一个实例化对象。
● 调用指定控制器中的相关方法。
表1-1说明了在ASP.NET 3.5 MVC应用项目中页面请求的执行过程。
表1-1 ASP.NET 3.5 MVC页面请求的执行过程
从表1-1中可以看出,当请求一个基于ASP.NET 3.5 MVC框架的网站页面时,主要包括5个步骤,它们分别是创建RouteTable、URL路由、执行MvcHandler、执行Controller和执行View()方法,下面详细说明这5个步骤。
2.5个执行步骤
在传统的ASP.NET应用程序中,每一个被请求的页面都对应着文件系统中的一个文件,否则就会出现错误。这些页面事实上都被表示为一个类,而该类实现了IHttpHandler接口,每当一个页面被请求时,就会调用该类中的ProcessRequest()方法,执行ProcessRequest()方法之后,就会将指定的内容返回到浏览器中。
在基于ASP.NET 3.5 MVC框架的网站中,每一个被请求的页面都被映射到相应的控制器中的相关方法,控制器负责将指定的内容返回到浏览器中。需要说明的是,多个页面可以被映射到同一个控制器中的不同方法。
在ASP.NET 3.5 MVC框架中,页面到控制器的映射是通过路径表(Route Table)而实现的,对于每一个应用程序有一个路径表。路径表通过RouteTable.Routes属性表示,在上述的代码清单1-1中,路径表中添加了1个路由对象,而路由对象负责实现URL到处理器的映射,该路由对象实现URL到MvcRouteHandler的映射,将具有{controller}/{Action}/{id}模式的URL映射到MvcRouteHandler。
需要说明的是,URL路由类库位于命名空间System.Web.Routing之中,与ASP.NET 3.5 MVC框架的命名空间System.Web.MVC是独立的,因此可以在非ASP.NET 3.5 MVC框架的网站中使用URL路由功能。
当请求一个基于ASP.NET 3.5 MVC框架的网站页面时,在Web.config配置文件中所配置的UrlRoutingModule模块解析该URL,并获得相关的RouteData对象,然后创建HttpHandler的实例化对象MvcHandler。
在执行MvcHandler时,调用其中的ProcessRequest()方法,执行该ProcessRequest()方法,从而创建一个控制器的实例化对象。
在执行Controller时,调用其中的Execute()方法,在该方法内部通过反射原理实现对指定其他方法的调用,在调用的方法中会执行View()方法,从而将指定页面的内容返回到浏览器中。
1.2.3 构建模型
在ASP.NET 3.5 MVC框架中,模型主要实现应用程序中的数据访问和业务逻辑,按照约定,这些模型类均存放在Models文件夹中,如果要显示NorthWind数据库中的Categories数据表,则需要创建ADO.NET实体数据模型。
在“解决方案资源管理器”窗口中的“MvcApplication ①”项目内的“Models”文件夹上单击鼠标右键,在弹出的快捷菜单中选择“添加”→“新建项”命令,打开如图1-9所示的“添加新项”对话框。
图1-9 “添加新项”对话框
在图1-9中,选择“ADO.NET Entity Data Model”模板,设置实体数据模型的名称为“Northwind.edmx”,然后单击“添加”按钮,打开如图1-10所示的对话框。所示的实体数据模型向导——“选择模型内容”对话框。
图1-10 “选择模型内容”对话框
在图1-10中,选择“从数据库生成”,表明ADO.NET实体框架从数据库直接生成实体数据模型,然后单击“下一步”按钮,打开如图1-11所示的“选择您的数据连接”对话框。
图1-11 “选择您的数据连接”对话框
在图1-11中,选择Northwind数据库的连接字符串,并设置相关的实体连接字符串,单击“下一步”按钮,就会打开如图1-12所示的“选择数据库对象”对话框。
图1-12 “选择数据库对象”对话框
在上述的“选择数据库对象”对话框中,根据需要可以选择数据表,也可以选择视图,还可以选择存储过程,这样ADO.NET实体框架就会自动生成这些数据库对象的实体数据模型。
这里只选择数据表“Categories”,如图1-13所示,然后单击“完成”按钮,ADO.NET实体框架即可生成该数据表所对应的实体数据模型,如图1-14所示。
图1-13 选择相关数据表
图1-14 实体数据模型
1.2.4 控制器
在ASP.NET 3.5 MVC框架中,控制器有着非常重要的作用,控制器处理用户的请求,将用户请求的URL路由,分发到控制器中的相关动作方法,而不是文件系统中某个对应的真实文件,这是ASP.NET 3.5 MVC应用程序与传统的Web Forms应用程序的区别之一。
1.默认的HomeController类
打开Controller文件夹下的HomeController.cs文件,ASP.NET 3.5 MVC框架默认创建了HomeController的实现代码,见代码清单1-3。
代码清单1-3 HomeController的实现代码
1: [HandleError] 2: public class HomeController : Controller 3: { 4: public ActionResult Index() 5: { 6: ViewData["Message"] = "Welcome to ASP.NET 3.5 MVC! "; 7: return View(); 8: } 9: 10: public ActionResult About() 11: { 12: return View(); 13: } 14: }
在上述代码中,控制器的名称必须命名为形如“XXXController”的格式,并且必须实现接口Icontroller,或者继承抽象类Controller类。
控制器中所定义的动作方法,处理用户的请求,执行其中相关的代码,例如检索或者更新数据库中的数据,然后选择相关的视图,将内容输出到浏览器中。
第1行代码设置了一个过滤器[HandleError],表示如果在执行该控制器中的有关方法出现异常的时候,将会打开友好的错误信息提示页面;在控制器中所定义的动作方法,必须设置为public;如果是一个内部方法,可以在该方法中设置过滤器[NonActionAttribute]。
第4行代码定义了一个动作方法Index(),该方法返回的类型是ActionResult。ActionResult是一个抽象类,因此实际的返回类型是该抽象类的子类,ActionResult的子类列表见表1-2。
表1-2 ActionResult的子类列表
第6行代码设置了ViewData的字典数据,以便将控制器中的指定数据,传递到视图;第6行代码调用Controller类中的View()方法,返回的对象是一个ViewResult的实例化对象,将指定的内容输出到浏览器中。
在Controller类中的相关方法与返回对象的列表,见表1-3。
表1-3 控制器中的方法与返回对象列表
2.修改后的HomeController类
本章需要实现Categories数据表的显示、编辑、添加,以及目录详细页面,因此在Home控制器中创建了相关的动作方法Index()、Edit()、Create()及Details(), HomeController类的UML类图,如图1-15所示。
图1-15 控制器的UML类图
HomeController类的实现代码,见代码清单1-4。
代码清单1-4 HomeController的实现代码
1: public class HomeController : Controller 2: { 3: NorthWindEntities Northwind = new NorthWindEntities(); 4: 5: public ActionResult Index() 6: { 7: var model= Northwind.Categories.ToList () ; 8: return View(model); 9: } 10: 11: [AcceptVerbs(HttpVerbs.Get )] 12: public ActionResult Edit(int id) 13: { 14: var model = Northwind.Categories.First(c => c.CategoryID == id); 15: return View(model); 16: } 17: 18: [AcceptVerbs(HttpVerbs.Post)] 19: public ActionResult Edit(int id, FormCollection form) 20: { 21: var model = Northwind.Categories.First(c => c.CategoryID == id); 22: UpdateModel(model, new [] {"CategoryName", "Description" } ); 23: 24: Northwind.SaveChanges(); 25: 26: return RedirectToAction("Index"); 27: } 28: 29: [AcceptVerbs(HttpVerbs.Get)] 30: public ActionResult Details(int id) 31: { 32: var model = Northwind.Categories.First(c => c.CategoryID == id); 33: return View(model); 34: } 35: 36: [AcceptVerbs(HttpVerbs.Get)] 37: public ActionResult Create() 38: { 39: Categories category = new Categories(); 40: return View(category ); 41: } 42: 43: [AcceptVerbs(HttpVerbs.Post)] 44: public ActionResult Create(int CategoryID, FormCollection form) 45: { 46: var model = Northwind.Categories. FirstOrDefault (c => c.CategoryID == CategoryID); 47: 48: if (model == null) 49: { 50: Categories category = new Categories(); 51: 52: UpdateModel(category, new[] { "CategoryName", "Description" }); 53: 54: Northwind.AddToCategories(category); 55: Northwind.SaveChanges(); 56: return RedirectToAction("Index"); 57: } 58: else 59: return RedirectToAction("Create"); 60: } 61: 62: public ActionResult About() 63: { 64: return View(); 65: } 66: }
在上述代码中,第3行新建一个ADO.NET数据实体模型的实例化对象Northwind,通过这个Northwind实例化对象,开发者就可以非常方便地实现数据的查询、添加、修改等数据操作。
第4行到第9行定义了Index()方法,第7行获得数据表Categories的数据列表,并通过第8行传递到对应的Index.aspx视图页面中,呈现数据表Categories的显示页面。
第12行到第16行定义了Edit()方法,第14行获得数据表Categories中指定目录编号(CategoryID)的数据,并通过第15行传递到对应的Edit.aspx视图页面中,呈现数据表Categories的编辑页面。
第18行到第27行定义了一个重载的Edit()方法,当用户在Categories的编辑页面中修改相关的数据之后,单击“Save”按钮时,就会执行该Edit()方法。第18行表示该方法只接受用户通过Post方法发送的表单数据;第21行获得数据表Categories中指CategoryID的数据;第22行通过UpdaeModel()方法,获得Categories编辑页面中用户所修改的Category Name和Description数据,并更新查询到的model数据;第24行将修改的数据,提交、返回到数据库中;最后通过第26行传递到对应的Index.aspx视图页面中,呈现数据表Categories的显示页面,其中显示被修改后的数据内容。
第29行到第34行定义了Details()方法,第32行获得数据表Categories中指定目录编号(CategoryID)的数据,并通过第33行传递到对应的Details.aspx视图页面中,呈现数据表Categories的详细页面。
第36行到第41行定义了Create()方法,第36行表示该方法只接受用户通过Get方法发送的表单数据;第39行创建一个新数据表Categories的实例化对象category,并通过第40行传递到对应的Create.aspx视图页面中,呈现数据表Categories的添加页面。
第43行到第60行定义了一个重载的Create()方法,当用户在Categories的添加页面中修改相关的数据之后,单击“Create”按钮时,就会执行该Create()方法。第43行表示该方法只接受用户通过Post方法发送的表单数据;第46行获得数据表Categories中指CategoryID的数据;第50行到第56行,解析用户添加的新数据,提交、返回到数据库中,最后传递到对应的Index.aspx视图页面中,呈现数据表Categories的显示页面,其中显示被添加后的数据内容。
当用户没有输入添加的内容时,第59行转移到Create.aspx视图页面中,等待用户继续输入添加的内容。
1.2.5 创建视图
创建了控制器HomeController类之后,就可以创建相关的视图页面。
1.显示页面
选择控制器HomeController中的Index()方法,然后单击鼠标右键,弹出如图1-16所示的快捷菜单。
图1-16 添加视图
在图1-16中,选择“Add View”命令,打开如图1-17所示的“Add View”对话框。
图1-17 “Add View”对话框
在“Add View”对话框中,选择“Create a strongly-typed view”复选框,在“View data class”下拉列表框中选择“MvcApplication1.Models.Categories”,表示视图中的数据来自于数据表Categories;在视图的内容“View content”下拉列表框中选择“List”,表示要创建的视图Index.aspx用于显示数据表Categories内容;选择相关的母版页及内容占位符的ID,单击“Add”按钮,ASP.NET 3.5 MVC框架就会自动创建一个视图Index.aspx页面。
Index.aspx页面的实现代码,见代码清单1-5。
代码清单1-5 Index.aspx页面的实现代码
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> 2: <h2>Index</h2> 3: <table> 4: <tr> 5: <th></th> 6: <th> 7: CategoryID 8: </th> 9: <th> 10: CategoryName 11: </th> 12: <th> 13: Description 14: </th> 15: </tr> 16: 17: <% foreach (var item in Model) { %> 18: <tr> 19: <td> 20: <%= Html.ActionLink("Edit", "Edit", new { id=item.CategoryID }) %> — <%= Html.ActionLink("Details", "Details", new { id=item.CategoryID })%> 21: </td> 22: <td> 23: <%= Html.Encode(item.CategoryID) %> 24: </td> 25: <td> 26: <%= Html.Encode(item.CategoryName) %> 27: </td> 28: <td> 29: <%= Html.Encode(item.Description) %> 30: </td> 31: </tr> 32: <% } %> 33: </table> 34: <p> 35: <%= Html.ActionLink("Create New", "Create") %> 36: </p> 37: </asp:Content>
在上述代码中,设置了一个表格来显示数据表Categories的数据。第4行到第15行设置了表格的第一行,分为4列;第17行到第32行通过典型的foreach循环语句,在从控制器传递到视图的Model数据中,分别读取Categories数据表中的CategoryID字段(第23行)、CategoryName字段(第26行)和Description字段(第29行)。
其中代码第20行使用HTML中的扩展方法ActionLink,设置“Edit”和“Details”链接,用于编辑指定记录或者查看该记录的详细信息;代码第35行设置“Created New”链接,用于添加新的记录。
视图Index.aspx的运行界面,如图1-18所示。
图1-18 Index.aspx的运行界面
在图1-18中,如果单击 “Edit”链接,就会进入目录编辑页面;如果单击 “Details”链接,就会进入目录详细页面;如果单击表格下方的“Create New”链接,则进入添加目录页面。
2.编辑页面
选择控制器HomeController中的Edit()方法,然后单击鼠标右键,在弹出的快捷菜单中选择“Add View”命令,打开如图1-19所示的“Add View”对话框。
图1-19 “Add View”对话框
在“View data class”下拉列表框中选择“MvcApplication1.Models.Categories”,表示视图中的数据来自于数据表Categories;而视图的内容“View content”则选择“Edit”,表示要创建的视图Edit.aspx可用于编辑数据表Categories内容;最后单击“Add”按钮,ASP.NET 3.5 MVC框架就会自动创建一个视图Edit.aspx页面。
Edit.aspx页面的实现代码,见代码清单1-6。
代码清单1-6 Edit.aspx页面的实现代码
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> 2: <h2>Edit</h2> 3: <%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %> 4: <% using (Html.BeginForm()) {%> 5: <fieldset> 6: <legend>Fields</legend> 7: <p> 8: <label for="CategoryID">CategoryID:</label> 9: <%= Html.TextBox("CategoryID", Model.CategoryID) %> 10: <%= Html.ValidationMessage("CategoryID", "*") %> 11: </p> 12: <p> 13: <label for="CategoryName">CategoryName:</label> 14: <%= Html.TextBox("CategoryName", Model.CategoryName) %> 15: <%= Html.ValidationMessage("CategoryName", "*") %> 16: </p> 17: <p> 18: <label for="Description">Description:</label> 19: <%= Html.TextBox("Description", Model.Description) %> 20: <%= Html.ValidationMessage("Description", "*") %> 21: </p> 22: <p> 23: <input type="submit" value="Save" /> 24: </p> 25: </fieldset> 26: <% } %> 27: 28: <div> 29: <%=Html.ActionLink("Back to List", "Index") %> 30: </div> 31: </asp:Content>
在上述代码中,第3行设置了一个验证摘要信息控件ValidationSummary,当表单的文本框中含有空白的输入时,该控件就会在表单页面中输出指定的错误提示信息。
第5行到第25行设置了一个边框,边框内以6行的方式分别显示了数据表Categories中的CategoryID标签和字段、CategoryName标签和字段及Description标签和字段的内容。
对于输入字段CategoryName,设置了文本框(第14行),还设置了验证信息控件(第15行),如果CategoryName字段输入为空白,则显示提示信息“*”;对于输入字段Description,设置了文本框(第19行),同样也设置了验证信息控件(第20行),如果Description字段输入为空白,则显示提示信息“*”。
在边框的下方,还有一个“Back to List”链接;如果单击“Back to List”链接,则会返回到目录显示页面。
视图Edit.aspx的运行界面如图1-20所示。在其中修改CategoryName或者Description的内容,然后单击“Save”按钮,就会保存修改的内容,并打开如图1-18所示的视图Index.aspx页面。
图1-20 Edit.aspx的运行界面
3.添加页面
选择控制器HomeController中的Create()方法,然后单击鼠标右键,在弹出的快捷菜单中选择“Add View”命令,打开“Add View”对话框。
在“View data class”下拉列表框中选择“MvcApplication1.Models.Categories”,表示视图中的数据来自于数据表Categories;而在视图的内容“View content”下拉列表框中选择“Create”,表示要创建的视图Create.aspx可用于添加数据表Categories内容;最后单击“Add”按钮,ASP.NET 3.5 MVC框架就会自动创建一个视图Create.aspx页面。
视图Create.aspx页面的实现代码,见代码清单1-7。
代码清单1-7 Create.aspx页面的实现代码
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> 2: <h2>Create</h2> 3: <%= Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %> 4: <% using (Html.BeginForm()) {%> 5: <fieldset> 6: <legend>Fields</legend> 7: <p> 8: <%= Html.Hidden ("CategoryID") %> 9: </p> 10: <p> 11: <label for="CategoryName">CategoryName:</label> 12: <%= Html.TextBox("CategoryName") %> 13: <%= Html.ValidationMessage("CategoryName", "*") %> 14: </p> 15: <p> 16: <label for="Description">Description:</label> 17: <%= Html.TextBox("Description") %> 18: <%= Html.ValidationMessage("Description", "*") %> 19: </p> 20: <p> 21: <input type="submit" value="Create" /> 22: </p> 23: </fieldset> 24: <% } %> 25: <div> 26: <%=Html.ActionLink("Back to List", "Index") %> 27: </div> 28: </asp:Content>
在上述代码中,设置了一个添加数据表Categories数据的表单。对于数据表Categories的主键,设置了隐藏的输入字段(第8行),对于输入字段CategoryName,设置了文本框(第12行),还设置了验证信息控件(第13行),如果CategoryName字段输入为空白,则显示提示信息“*”;对于输入字段Description,设置了文本框(第17行),同样也设置了验证信息控件(第18行),如果Description字段输入为空白,则显示提示信息“*”;并在第3行设置了验证摘要信息控件,只要两个文本框中的任何一个出现空白输入,则提示用户指定的错误信息。
视图Create.aspx的运行界面,如图1-21所示。
图1-21 Create.aspx的运行界面
在图1-21中,添加CategoryName和Description的内容,然后单击“Create”按钮,就会添加一条新的记录,并打开如图1-18所示的视图Index.aspx页面。
4.详细页面
选择控制器HomeController中的Details()方法,然后单击鼠标右键,在弹出的快捷菜单中选择“Add View”命令,就会打开一个“Add View”对话框。
在“View data class”的下拉列表框中选择“MvcApplication1.Models.Categories”,表示视图中的数据来自于数据表Categories;而在视图的内容“View content”下拉列表框中选择“Details”,表示要创建的视图Details.aspx可用于添加数据表Categories内容;最后单击“Add”按钮,ASP.NET 3.5 MVC框架就会自动创建一个视图Details.aspx页面。
Details.aspx页面的实现代码,见代码清单1-8。
代码清单1-8 Details.aspx页面的实现代码
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> 2: <h2>Details</h2> 3: <fieldset> 4: <legend>Fields</legend> 5: <p> 6: CategoryID: 7: <%= Html.Encode(Model.CategoryID) %> 8: </p> 9: <p> 10: CategoryName: 11: <%= Html.Encode(Model.CategoryName) %> 12: </p> 13: <p> 14: Description: 15: <%= Html.Encode(Model.Description) %> 16: </p> 17: </fieldset> 18: <p> 19: <%=Html.ActionLink("Edit", "Edit", new { id=Model.CategoryID }) %> — <%=Html.ActionLink("Back to List", "Index") %> 20: </p> 21: </asp:Content>
在上述代码中,设置了一个边框(第3行到第17行),边框内以3行的方式分别显示了数据表Categories中的CategoryID字段、CategoryName字段和Description字段的内容;在边框的下方,还有一个“Edit”链接和一个“Back to List”链接。
如果单击“Edit”链接,则会进入目录编辑页面;如果单击“Back to List”链接,则会返回到目录显示页面。
视图Details.aspx的运行界面,如图1-22所示。
图1-22 Details.aspx的运行界面
1.3 URL路由
所谓URL路由(URL Routing),指的是在基于ASP.NET 3.5 MVC的网站中,URL不再是文件目录中的一个文件,而是一个说明有关URL路由的字符串,开发者可以自行定义该字符串的格式,方便使用者理解相关页面的功能。
在URL路由中,首先需要定义URL路由,该URL路由通过占位符定义URL的模式,URL路由将用户请求的URL路由解析为一系列的离散值。例如,对于一个URL请求http://server/application/Products/show/beverages来说,URL路由将解析后的离散值Products、show和beverages值发送到相关的处理程序,而对于传统的ASP.NET应用程序来说,/Products/show/beverages部分只不过是一个文件的部分路径而已。
1.3.1 URL路由设置
URL路由是与ASP.NET 3.5 MVC框架独立的一个功能,也就是说,开发者可以在传统的ASP.NET应用程序中使用URL路由。
1.定义URL路由
定义URL路由,就是设置URL模式。在URL路由中,通过大括号({ })定义占位符,这些占位符就是URL路由参数,而字符串中的“/”、“.”等符号则作为分隔符被URL路由解析这些离散的数据,对于不在小括号或者方括号中的信息则被视为一个常量。
表1-4说明了如何定义URL路由。
表1-4 定义URL路由
从表1-4中可以看出,第1行定义了含有3个URL路由参数的URL路由,此时Products就是控制器的名称,show就是该控制器中所定义的一个方法,而beverages则是一个id变量;对于第2行所定义的URL路由来说,Products是一个数据表名称,而Details.aspx则是一个常量;第3行定义了含有2个URL路由参数的URL路由,此时blog是一个常量,show是相关控制器中所定义的一个方法,而123则是一个entry变量;第4行定义了含有4个URL路由参数的URL路由,此时sales是一个reporttype变量,2008是一个year变量,1是一个month变量,5则是一个day变量。因此,通过定义URL路由,非常有利于对相关页面功能的理解。
开发者一般通过Global.asax文件,在Application_Start()方法中设置上述URL路由的定义,这样当应用程序运行时,就保证了URL路由被成功设置,同时也使得测试程序可以调用该方法。
开发者通过静态类RouteTable的属性Routes来设置URL路由,代码清单1-9定义了URL路由的实现代码。
代码清单1-9定义URL路由的实现代码
1: protected void Application_Start(object sender, EventArgs e) 2: { 3: RegisterRoutes(RouteTable.Routes); 4: } 5: 6: public static void RegisterRoutes(RouteCollection routes) 7: { 8: routes.Add(new Route 9: ( 10: "Category/{Action}/{categoryName}", new CategoryRouteHandler() 11: )); 12: }
在上述代码中,第3行调用第6行到第12行所定义的RegisterRoutes()方法,在该方法中定义了一个URL路由,其中定义了两个URL路由参数,它们分别是Action变量和categoryName。
当URL路由处理URL请求时,首先去寻找匹配的URL路由,例如被请求的URL为http://server/application/Category/Show/Tools时,根据代码1-9中所定义的URL路由,被请求的URL是匹配该URL路由的,因此URL路由参数中的Action变量为对应控制器中的Show()方法,categoryName变量则为Tools。
如果被请求的URL为http://server/application/Category/Add,此时该URL与代码1-9中所定义的URL路由不匹配,并且也没有定义默认的URL路由,那么此时的URL路由将不会处理该URL请求,这个URL请求被当做普通的页面由传统的ASP.NET应用程序处理。
这里还需要说明的是,在所定义的URL路由集合中,URL路由的排列顺序是非常重要的,当URL路由根据URL请求去寻找匹配的URL路由时,一旦寻找到第一个匹配的URL路由,就不再继续寻找剩下的URL路由了。
2.设定URL路由参数的默认值
当定义URL路由时,开发者还可以设置URL路由参数的默认值,如果匹配的URL请求中没有包括相关的URL路由参数,那么这些URL路由参数就会使用这些默认值。
设定URL路由参数默认值的实现代码,见代码清单1-10。
代码清单1-10设定URL路由参数默认值的实现代码
1: void Application_Start(object sender, EventArgs e) 2: { 3: RegisterRoutes(RouteTable.Routes); 4: } 5: 6: public static void RegisterRoutes(RouteCollection routes) 7: { 8: routes.Add(new Route 9: ( 10: "Category/{Action}/{categoryName}", new CategoryRouteHandler() 11: ) 12: { 13: Defaults = new RouteValueDictionary {{"categoryName", "food"}, {"Action", "show"}} 14: } 15: ); 16: }
在上述代码中,第13行创建了所定义URL路由参数的默认值,即categoryName变量的默认值是food,而Action方法则是对应控制器中的show()方法。
表1-5说明了如何使用URL路由参数的默认值。
表1-5 URL路由参数的默认值
从表1-5中可以看出,第1行中被请求的URL中没有包括任何URL路由参数,因此URL路由将使用设定的默认值,此时categoryName变量的默认值是food,而Action方法则是对应控制器中的show()方法;第2行中被请求的URL中包括一个URL路由参数,因此URL路由解析该URL后,此时categoryName变量的默认值是food,而Action方法则是对应控制器中的Add()方法;第3行中被请求的URL中包括完整的URL路由参数,因此URL路由解析该URL后,此时categoryName变量的默认值是beverages,而Action方法则是对应控制器中的Add()方法。
3.设定URL路由通配符
在定义URL路由时,为了实现对一类URL路由的定义,可以使用星号(*)来定义URL路由通配符。
假如设定的URL路由通配符为:query/{queryname}/{*queryvalues},表1-6说明了如何使用URL路由通配符。
表1-6 URL路由通配符
从表1-6中可以看出,第1行中被请求的URL中包括了完整的URL路由参数,因此该URL的通配符参数值是字符串"bikes/onsale";对于第2行中被请求的URL来说,该URL的通配符参数值是字符串"bikes";而对于第3行中被请求的URL来说,该URL的通配符参数值则是空白的字符串。
4.添加URL路由参数的约束
在定义URL路由参数时,开发者还可以根据实际需要设定这些参数的约束,以便保证这些URL路由参数为指定的类型,如日期、电话数字等,添加URL路由参数约束的实现代码,见代码清单1-11。
代码清单1-11添加URL路由参数约束的实现代码
1: void Application_Start(object sender, EventArgs e) 2: { 3: RegisterRoutes(RouteTable.Routes); 4: } 5: 6: public static void RegisterRoutes(RouteCollection routes) 7: { 8: routes.Add(new Route 9: ( 10: "{locale}/{year}" , new ReportRouteHandler() 11: ) 12: { 13: Constraints = new RouteValueDictionary {{"locale", "{a-z}{2}-{A-Z}{2}"}, {year, @"\d{4}"}} 14: }); 15: }
在上述代码中,第13行添加了对URL路由参数locale及year的约束,其中locale必须是英文字母,前面的2位英文字母必须是小写的,后面的2位英文字母必须是大写的,而year必须是4位数字。
表1-7说明了如何使用添加的URL路由参数约束。
表1-7 URL路由参数约束
从表1-7中可以看出,第1行中被请求URL的locale变量中由于要求第4位、第5位的英文字母必须是大写的,因此该URL请求不匹配URL路由参数的约束;第2行被请求URL的year变量中由于要求4位年份数字,因此该URL请求也不匹配URL路由参数的约束;而只有第3行被请求URL的locale变量及year变量满足所定义的约束,因此locale变量为字符串"en-US", year变量为字符串"2008"。
1.3.2 使用URL路由
在ASP.NET 3.5 MVC框架中,使用URL路由将请求的URL映射到控制器中的相关方法。URL路由解析URL路由,并将URL路由参数传递到控制器中的相关方法,下面说明在ASP.NET 3.5 MVC框架中如何使用URL路由。
1.设定默认的URL路由
在通过ASP.NET 3.5 MVC项目模板所建立的一个基本MVC网站中,在Global.asax文件中就已经设定了默认的URL路由,以便开发者即刻运行所建立的MVC网站。
表1-8说明了所设定的默认URL路由。
表1-8 默认的URL路由
从表1-8中可以看出,第1行设定了URL路由应当包括3个部分的路径参数,对于右边匹配的URL来说,对应的控制器名称为ProductsController,控制器ProductsController中的执行方法为show()方法,而id变量则为beverages;第2行设定了默认的URL路由为Default.aspx页面,该页面所对应的URL为http://server/application/Default.aspx。
在设定了默认路径之后,如果被请求的URL没有包括相关的URL路由参数,那么ASP.NET 3.5 MVC框架将会设置默认的URL路由参数,表1-9说明了如何使用默认的URL路由参数。
表1-9 默认的URL路由参数
从表1-9中可以看出,如果使用第1行所定义的URL路由,那么此时默认的执行方法为Index()方法,而id变量则为null;如果被请求的页面为Default.aspx,那么ASP.NET 3.5 MVC框架使用默认的控制器为HomeController,默认的执行方法为Index()方法,而id变量则为null。
设定默认URL路由的实现代码,见代码清单1-12。
代码清单1-12设定默认URL路由的实现代码
1: RouteTable.Routes.Add(new Route 2: { 3: "{controller}/{Action}/{id}", new MvcRouteHandler()) { Defaults = new RouteValueDictionary(new { Action = "Index", id = "" }), 4: }); 5: 6: RouteTable.Routes.Add(new Route 7: ( 8: "Default.aspx", new MvcRouteHandler()) { Defaults = new RouteValueDictionary(new { controller = "Home", Action = "Index", id = "" }), 9: });
在上述代码中,第3行创建了一个形如{controller}/{Action}/{id}的URL路由,其中设置了URL路由参数中的Action变量的默认值为Index,而id变量的默认值为null;第8行创建了Default.aspx页面路径,设置了控制器controller变量的默认值为Home, Action变量的默认值为Index,而id变量的默认值为null。
2.URL路由的映射
当一个URL被请求时,ASP.NET 3.5 MVC框架首先使用UrlRoutingModule模块来解析该URL地址,然后通过MvcHandler对象,选择相关的控制器及控制器中的相关方法来处理用户的请求。
如果被请求的URL为http://server/application/Products/show/beverages,此时该URL被解析后,controller变量为Products,动作(Action)方法为show()方法,因此最后选择的控制器应该为ProductsController,而动作方法则是ProductsController中的show()方法,这样就实现了URL路由到控制器及控制器中相关动作方法的映射。
需要说明的是,控制器类必须实现System.Web.MVC.IController接口,而且控制器的名称约定为以“Controller”结束,如ProductsController、OrdersController等。实现控制器的通常方法是继承System.Web.MVC.Controller这个基类,然后添加相关的方法对应URL路由中的Action参数。
1.4 思考与提高
本章实现了在ASP.NET 3.5 MVC框架下,NorthWind数据库中的数据表Categories内容的显示、修改、添加及详细页面,是ASP.NET 3.5 MVC框架下的一个典型应用。说明了如何构建模型、如何实现控制器及如何创建对应的视图;特别说明了URL路由,这是初学者学习ASP.NET 3.5 MVC框架的难点之一。
请读者仔细阅读代码清单1-1和代码清单1-10中关于设置URL路由的代码,一种方法是利用MapRoute()方法,另外一种是利用Add()方法,请比较这两种方式的异同,理解为什么能够这样设置。