第1篇 体验篇

active=true

第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所示。

active=true

图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中模型、视图及控制器之间的相互关系。

active=true

图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所示的“新建项目”对话框。

active=true

图1-3 “新建项目”对话框

在“新建项目”对话框中,选择项目模板“ASP.NET MVC Web Application”,设置项目的名称为“MvcApplication1”,然后单击“确定”按钮,此时ASP.NET 3.5 MVC框架就会弹出一个“Create Unit Test Project”对话框,如图1-4所示。

active=true

图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所示。

active=true

图1-5 ASP.NET 3.5 MVC应用项目

运行上述MvcApplication1网站,打开如图1-6所示的启动界面。

active=true

图1-6 启动界面

在图1-6中,如果单击导航菜单中的“About”链接,就会打开如图1-7所示的About界面。

active=true

图1-7 About界面

1.2.1 约定的目录结构

通过项目模板“ASP.NET MVC Web Application”创建MvcApplication1网站时,根据ASP.NET 3.5 MVC框架的约定,MvcApplication1网站将模型、视图和控制器组件及其他内容分别安放在不同的项目目录中,以便开发者维护与管理,MvcApplication1网站的目录结构如图1-8所示。

active=true

图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页面请求的执行过程

active=true

从表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所示的“添加新项”对话框。

active=true

图1-9 “添加新项”对话框

在图1-9中,选择“ADO.NET Entity Data Model”模板,设置实体数据模型的名称为“Northwind.edmx”,然后单击“添加”按钮,打开如图1-10所示的对话框。所示的实体数据模型向导——“选择模型内容”对话框。

active=true

图1-10 “选择模型内容”对话框

在图1-10中,选择“从数据库生成”,表明ADO.NET实体框架从数据库直接生成实体数据模型,然后单击“下一步”按钮,打开如图1-11所示的“选择您的数据连接”对话框。

active=true

图1-11 “选择您的数据连接”对话框

在图1-11中,选择Northwind数据库的连接字符串,并设置相关的实体连接字符串,单击“下一步”按钮,就会打开如图1-12所示的“选择数据库对象”对话框。

active=true

图1-12 “选择数据库对象”对话框

在上述的“选择数据库对象”对话框中,根据需要可以选择数据表,也可以选择视图,还可以选择存储过程,这样ADO.NET实体框架就会自动生成这些数据库对象的实体数据模型。

这里只选择数据表“Categories”,如图1-13所示,然后单击“完成”按钮,ADO.NET实体框架即可生成该数据表所对应的实体数据模型,如图1-14所示。

active=true

图1-13 选择相关数据表

active=true

图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的子类列表

active=true

第6行代码设置了ViewData的字典数据,以便将控制器中的指定数据,传递到视图;第6行代码调用Controller类中的View()方法,返回的对象是一个ViewResult的实例化对象,将指定的内容输出到浏览器中。

在Controller类中的相关方法与返回对象的列表,见表1-3。

表1-3 控制器中的方法与返回对象列表

active=true
2.修改后的HomeController类

本章需要实现Categories数据表的显示、编辑、添加,以及目录详细页面,因此在Home控制器中创建了相关的动作方法Index()、Edit()、Create()及Details(), HomeController类的UML类图,如图1-15所示。

active=true

图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所示的快捷菜单。

active=true

图1-16 添加视图

在图1-16中,选择“Add View”命令,打开如图1-17所示的“Add View”对话框。

active=true

图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所示。

active=true

图1-18 Index.aspx的运行界面

在图1-18中,如果单击 “Edit”链接,就会进入目录编辑页面;如果单击 “Details”链接,就会进入目录详细页面;如果单击表格下方的“Create New”链接,则进入添加目录页面。

2.编辑页面

选择控制器HomeController中的Edit()方法,然后单击鼠标右键,在弹出的快捷菜单中选择“Add View”命令,打开如图1-19所示的“Add View”对话框。

active=true

图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页面。

active=true

图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所示。

active=true

图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所示。

active=true

图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路由

active=true

从表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路由参数的默认值

active=true

从表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路由通配符

active=true

从表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路由参数约束

active=true

从表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路由

active=true

从表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路由参数。

active=true

表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()方法,请比较这两种方式的异同,理解为什么能够这样设置。