唉、本来这一篇前几天早就应该发了的,可是谁每月没有那么几天啊。。。
呵呵。开个玩笑。反正就是各种烦气,所以也就一直没上来继续发了。
年底了,摆正一下心态吧。好好干,整点钱,过年回家能跟亲朋好友好好装装逼啊是不。哈哈。
1.控制器的角色
1.1控制器简史
2.控制器基础
2.1简单示例:HomeController
2.2创建第一个控制器
2.2.1创建新控制器
2.2.2编写操作方法
2.2.3经验总结
3.小结
本篇将阐述ASP.NET MVC中的控制器是如何响应用户的HTTP请求 并将处理的结果信息返回给浏览器,重点介绍和演示控制器和控制器操作的功能。
由于咱们还没涉及到视图和模型(也就是MVC中的V和M),所以本篇涉及到的视图和模型的内容算是超前的,大家瞅瞅就行了,后面会单独着重介绍。
正好也为后面学习打下一点点基础。
在前面几篇内容中,咱们概括性的介绍了MVC模式,然后对ASP.NET Web Forms和ASP.NET MVC进行了比较。
那么下面,也就是本篇内容,我们将深入学习MVC模式中的三个核心元素之一 —— 控制器。
讨论一个问题最好的方式是从其定义开始,然后在深入讨论其细节。
在本篇内容中,牢记控制器的定义,这将让你理解控制器的含义及其应用有非常大的帮助。
MVC模式中的控制器(Controller)主要负责响应用户的输入,并且在响应时修改模型(Model)。
通过这种方式,MVC模式中的控制器主要关注的就是 应用程序流 、 输入参数的处理 ,以及 对相关视图(View)输出数据的提供 了。
过去的Web服务器支持访问以静态文件存储在磁盘上的HTML页面。随着动态网页的盛行,Web服务器也支持由存储在服务器上的动态脚本生成的HTML页面了。
MVC则略有不同,请求的URL地址首先告知路由机制(后面的内容将会详细介绍)去实例化哪个控制器、调用哪个操作方法、并为该方法提供需要的参数。然后控制器的方法决定使用那个视图,并对该视图进行渲染。
URL并不与存储在Web服务器磁盘上的文件有直接对应关系,而是与控制器类的方法有关。
ASP.NET MVC对MVC模式中的前端控制器进行了改造,正如咱们后面要学习的,路由系统在前面,之后才是控制器。
理解MVC模式在Web场景中工作原理的便捷方法就是记住、多练!
MVC提供的是方法调用的结果,而不是动态生成的页面。
1.1 控制器简史
MVC已经出现了很长一段时间 —— 可以追溯到现代Web应用程序时代来临前的几十年。
当MVC第一次开发出来的时候,图形用户界面(GUI)才刚刚起步,并且在不断演化发展。
当时,当用户按下一个按键或单击屏幕时,某个进程会“监听到”他们的动作,这个进程就是控制器。
控制器主要负责接收和解释输入,并更新任何需要的数据类(模型Model),然后通知用户进行的修改或程序更新界面显示(视图View)。
20世纪70年代末和80年代初,Xerox PARC(刚好也是MVC模式诞生的地方)的研究员考试研究GUI的概念,在GUI中用户“工作”在一个虚拟的“桌面”环境中,在这种环境下,用户可以单击和来回拖拽物件。
从这里产生了事件驱动编程的思想 —— 根据用户触发的事件(如单击或是敲击键盘上的按键)来执行响应的程序操作。
后来,随着GUI成为规范,MVC模式就不完全地适合这些新系统了,这一点变得越来越明显了。
在此类系统中,由GUI组件负责处理用户输入,比如当按下一个按钮时,是该按钮本身响应鼠标单击,而不是控制器。
按钮转而将以此通知所有单击的观察者或侦听者它被单击了。
相对于MVC模式而言,另一些模式,比如模型-视图-表示器(Model-View-Presenter,MVP)则表现的与这些现代系统更贴近。
ASP.NET Web Forms是一个基于事件的系统,这在Web应用程序中是独一无二的。它拥有一个强大的基于控件和事件驱动的编程模型,从而为开发人员进行Web开发提供了一个良好的组件化GUI。
当单击一个按钮时,Button控件将会做出响应,并在服务器端引发一个事件以告知它被单击。
这种方法的秒出在于 它可以让开发人员在更高的抽象级别下编写代码 。
不过,进行更深入的分析则会发现,靠站的很多工作都是在模拟这种组件化的事件驱动。
然而本质上,当单击一个按钮时,浏览器将向包含了页面上控件状态的服务器提交一个请求,控件所在的页面会被封装在一个编码的隐藏输入中。
在服务器端,为了响应该请求,ASP.NET必须重建整个控件层次结构,然后解释请求,并利用请求的内容来恢复应用程序中用户的当前状态。
究其本质,所有这些都是因为Webb是无状态的。 因此,当使用富客户端的Windows GUI应用程序时,没必要每当用户单击一个UI小部件时就重建整个屏幕和控件层级结构, 因为应用程序保持了原状态,不曾改变。
对于Web程序而言,用户的应用程序状态实质上是消失的。只不过是后来用户每次单击后都会恢复。
虽然这会极大地简化程序,但是以HTML形式出现的用户界面需要从服务器发送到客户端浏览器。这就引发一个问题:“应用程序在哪里?”。
对于大多数的Web页面而言,应用程序就在客户端和服务器之间交互。每次都维持一个小状态,可能是客户端的一个cookie或是服务器上的一块内存,一切都被小心的设计用来掩盖一个小小的“谎言”,这个“谎言”就是Internet和HTTP之间可以进行有状态的编程。不管怎么掩盖都是明显的扯犊子。
当进行Web开发时,事件驱动编程方法(即“状态”概念)的支撑作用将不复存在,并且许多人不愿接受这个虚拟有状态平台的“谎言”。鉴于此,业界已经见证了MVC模式的复兴(尽管对其做了一点轻微的改动)。
下面给出一个改动的示例。在传统的MVC模式中,模型可以通过与视图的间接联系来“观察”视图,这就允许模型根据视图的事件来进行自我调整。
对于在Web开发中应用MVC模式而言,当视图被发送到客户端浏览器时,模型通常已经不再内存当众,所以不再能观察视图上的事件(后面当我们讨论Ajax的时候,将会看到这个例外)。
在Web开发中采用MVC模式,控制器再次走在了前列。应用MVC模式要求Web应用程序中的每一个用户输入只采用请求的方式。
例如,在ASP.NET MVC中,每个请求都被路由转到控制器的一个方法,该控制器全权负责解释这些请求,如有必要,还要操纵模型数据,然后选择一个视图反馈给用户。
上面扩展了一部分理论知识,接下来深入讲解ASP.NET MVC控制器的具体实现。我们将继续使用前面已经创建好的项目。
如果您跳过了直接看本篇的,回头瞅瞅就行了。
在MVC入门时会遇到像先有鸡还是先有蛋这样的问题,需要理解三个部分(模型、视图和控制器),但在不理解其他部分的情况下,要深入其中一部分是很难的。
因此,再开始学习MVC时,需要首先概括地了解控制器,暂时先不管模型和视图。
讲解了控制器的基本工作原理之后,然后咱们再深入学习视图、模型和其他ASP.NET MVC开发主题。最后的文章中,咱们还会再学习高级控制器。
再开始实质性地编写代码之前,首先了解一下在一个新的项目中默认都包含那些内容。
用Internet Application模板创建的项目包含两个控制器类:
在Visual Studio的项目中,展开/Controller文件夹,打开HomeController.cs文件,如图1所示:
图1 - 控制器 HomeController.cs
注意这是一个相当简单的类,它继承了Controller基类。HomeController类的Index方法负责决定当浏览网站首页时触发的事件。
下面按照以下步骤对程序进行简单的修改,然后运行程序。
(1)用自己想要的短语替换About方法中的“Your application description page.”,比如“Boom、ShaKaLaKa!”。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 7 namespace BookStore.Controllers 8 { 9 public class HomeController : Controller 10 { 11 public ActionResult Index() 12 { 13 return View(); 14 } 15 16 public ActionResult About() 17 { 18 ViewBag.Message = "Boom、ShaKaLaKa!"; 19 20 return View(); 21 } 22 23 public ActionResult Contact() 24 { 25 ViewBag.Message = "Your contact page."; 26 27 return View(); 28 } 29 } 30 } 修改About方法后的HomeController.cs
(2)按下F5键或者使用“调试 - 启动调试”菜单项运行应用程序,Visual Studio编译应用程序并启动运行在IIS Express下的站点。
Visual Studio 2013包括IIS Express,这是IIS的本地开发版本,可以用来在一个随机的空闲端口上运行网站。
如图2,网站在“http://localhost:5831/”上运行,因此它采用的端口号是5831,你运行时的端口号可能与这个不同。
Visual Studio 2010及其以下版本使用的是Visual Studio Development Swerver(有时也称它的老代号Cassini),而不是IIS Express。
尽管Development Server很像IIS,但IIS Express实际上是IIS的优化版本,优化后使它更适用于开发。
想更多地了解IIS Express,请自行Google。
(3)接下来,会打开一个浏览器窗口,显示网站的首页,如图2所示:
图2 - 初次启动后的网站首页
(4)点击导航菜单的“关于”,浏览到/Home/About,打开关于页面,你修改的消息就显示出来了,如图3:
图3 - 显示修改后的信息“Boom、ShaKaLaKa!”
现在已经创建了一个新项目并在屏幕上显示了指定的信息,接下来通过创建一个新的控制器来创建一个实际的应用程序。
首先创建一个控制器来处理有关浏览图书目录的URL。这个控制器支持一下三个功能:
创建控制器,首先需要添加一个新的StoreController类。具体方法如下:
(1)右击“解决方案资源管理器”下项目里面的“Controller”文件夹,右键“添加 - 控制器”,如图4所示;
(2)模板类型选择“MVC5 控制器 - 空”,如图5所示;
(3)强控制器命名为“StoreController”,然后单击添加按钮,如图6所示。
图4 - 右键项目Controller文件夹 - 新建控制器
图5 - 选择控制器类型
图6 - 输入控制器名称
新创建的StoreController控制器已经有了一个Index()方法,下面将利用这个Index()方法实现在页面上列出图书商店里面所有图书类别的功能。
另外,还需要添加两个额外的方法来实现上面说到的其他两项功能,分别是Category()和Details()。
控制器中的这些方法(Index()、Category()、Details())成为控制器操作。
正如上述的HomeController.Index()操作方法那样,控制器操作的是响应URL请求,执行相应的操作,并向浏览器或是单击这个URL的用户做出响应。
要了解控制器操作的工作原理,可按照以下的步骤操作:
(1)将Index()方法的返回数据类型改为 string (而不是 ActionResult );
然后将返回值 return View(); 改为 return "请求来自:Store.Index() ! " ; 。
修改后的StoreController.cs如下所示:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 7 namespace BookStore.Controllers 8 { 9 public class StoreController : Controller 10 { 11 public string Index() 12 { 13 return "请求来自:Store.Index() !"; 14 } 15 } 16 }
(2)添加对商店的Category()方法,将返回设置为 return " 请求来自:Store.Category() ! " ; ;
添加浏览详细信息的Details()方法,将返回设置为 return " 请求来自:Store.Details() ! " ; 。
控制器StoreController的完整代码如下所示:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 7 namespace BookStore.Controllers 8 { 9 public class StoreController : Controller 10 { 11 public string Index() 12 { 13 return "请求来自:Store.Index() !"; 14 } 15 16 public string Category() 17 { 18 return "请求来自:Store.Category() !"; 19 } 20 21 public string Details() 22 { 23 return "请求来自:Store.Details() !"; 24 } 25 } 26 }
(3)重新运行项目,然后浏览一下URL:
访问这些URL会调用控制器中的操作方法,然后返回相应字符串,如图7、8、9所示:
图7 - 浏览/Store
图8 - 浏览/Store/Category
图9 - 浏览/Store/Details
从以上这个姜丹实验中可以得出以下几个结论:
前面的例子返回输出的是常量字符串。 下一步就是让它们通过响应URL传进来的参数动态地执行操作。按以下步骤来实现:
(1)把Category()操作方法修改为,查找URL传过来的查询字符串值。
可以通过在操作方法中添加一个string类型的“categoryName”参数来实现这个功能。
然后,当这个方法被调用时,ASP.NET MVC会自动将名为“categoryName”的查询字符串或表单提交参数传递给Category()操作方法。
1 public string Category(string categoryName) 2 { 3 string message = HttpUtility.HtmlEncode("Store.Category(),CategoryName=" + categoryName); 4 return message; 5 }
利用方法HttpUtility.HtmlEncode()来预处理用户输入。这样就能阻止用户用链接向视图中注入Javascript代码或HTML标记,也就可以防止XSS注入了。
例如用户访问: /Store/Category?categoryName=<script>window.location= ' http://www.geeksss.com ' </script> ,甚至插入其他恶意Javascript等等。
(2)上面的操作完事儿了之后,我们浏览 /Store/Category?categoryName=Java ,结果如图10所示:
图10 - 浏览Category()操作方法,并传递参数categoryName=Java
这表明控制器操作可以将查询字符串作为其操作方法的参数来接收。
(3)修改Details()操作方法,使其读取和显示一个名为ID的输入参数。
这里不像前面的方法那样把ID值作为一个查询字符串参数,而是将ID直接嵌入到URL中,如: /Store/Details/ 520 。
ASP.NET MVC再不需要任何额外配置的情况下可以很容易地做到这一点。
ASP.NET MVC的默认路由约定,就是将操作方法名称后面URL的这个片段( 例如上面的 /Store/Details/ 520 中的520)作为一个参数,该参数的名称为ID。
如果操作方法中又名为ID的参数,那么ASP.NET MVC会自动将这个URL片段作为ID参数传递过来。
1 public string Details(int id) 2 { 3 string message = "Store.Details(),ID=" + id; 4 return message; 5 }
(4)运行应用程序,浏览到/Store/Details/520,如图11所示:
图11 - 为Details()操作方法添加id参数并访问
像前面示例演示的那样,控制器操作感觉就像是Web浏览器直接调用控制器类中对应的操作方法。
类、操作方法和参数都被具体化为URL中的特定路径片段或查询字符串,结果就是一个返回给浏览器的字符串。
这就是进行了极大的简化,而忽略了下面这些的细节:
控制器提供了很多自定义和扩展的功能,但是我们很少能用到这些内容。
在一般应用程序中,控制器通过URL被调用,然后执行自定义的代码并返回一个视图。先记住这些内容,后面我们会详细介绍关于控制器如何定义、调用和扩展的底层细节。
现在已经学习了足够的控制器知识,下面就可以与视图结合起来使用了,下一篇我们将对此进行详细的介绍。