2.9 控制器
2.9.1 认识控制器
在ASP.NET MVC中,控制器包括了执行以下操作的应用程序逻辑:操作模型、处理用户交互,以及选择合适的视图显示在浏览器中。控制器可以看作黏合模型和视图的胶水。
实际上,控制器只是从System.Web.Mvc.IController接口继承而来的一个类对象。但是,最常见的控制器实现是利用已经实现的Controller类从Icontroller接口抽象出来。设计合理的控制器一般会包括一个或多个动作的方法。
ASP.NET MVC控制器负责响应对ASP.NET MVC网站发起的请求。每一个浏览器请求都映射到了一个专门的控制器。假如我们在浏览器地址栏输入了下面的URL:http://localhost/User/Index。
在这种情况下,ASP.NET MVC框架将会调用一个名为UserController的控制器。它负责完成对浏览器请求的响应。控制器可能会返回一个特定的视图,或者是将重定向到另一个控制器。
你可以在解决方案中的任何目录上右击,在弹出的快捷菜单中依次选择“添加”→“控制器”命令(详细操作方法参考2.3节)。
一个控制器类包括了动作、结果、筛选器和选择器等内容,下面做详细讲解。
2.9.2 控制器的动作
控制器的动作(Action)实际上是指一个带有参数、返回值和属性的标准的.NET方法,这个方法与代码中的其他方法无本质区别。一个方法能成为动作,是因为它是一个定义于控制器内部的方法,如UserController类中的Index()、Create()、Create(User model)方法均是动作。控制器动作的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using LMS.Models; namespace LMS.Content { public class UserController : Controller { //GET: /User/ public ActionResult Index() { return View(); } [AcceptVerbs(HttpVerbs.Get)] //操作选择器,接受HttpGet请求 [OutputCache(Duration=600)] //操作筛选器,设置页面缓存的时间 public ActionResult Create() { User model =new User(); //实例化模型 return View(model); //将模型交给视图处理 } [AcceptVerbs(HttpVerbs.Post)] //操作选择器,接受HttpGet请求 public ActionResult Create(User model) { return View(); } } }
2.9.3 控制器的结果
控制器的结果实际上是方法返回值的名称。在控制器中,动作的返回值类型是ActionResult,或是一个继承于ActionResult父类的对象。
ASP.NET MVC框架支持多种标准类型的动作结果,如表2-12所示。
表2-12 ASP.NET的动作结果类型
在UserController类的代码中,操作的结果类型都是ActionResult。但是,实际上操作返回的是一个从ActionResult继承而来的名为ViewResult的对象。使用控制器中定义的受保护方法View()创建ViewResult, View()方法可以完成带返回的ViewResult的全部实例化工作。
通常情况下,并不直接返回一个动作的结果,而是调用Controller基类的方法之一,如表2-13所示。
表2-13 ASP.NET的动作结果的调用方法
调用的示例代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using LMS.Models; namespace LMS.Content { public class UserController : Controller { //GET: /User/ [OutputCache(Duration=600)] //操作筛选器,设置页面缓存的时间 public ActionResult Index() { return View(); } [AcceptVerbs(HttpVerbs.Get)] //操作选择器,接受HttpGet请求 public ActionResult Create() { User model =new User(); //实例化模型 return View(model); //将模型交给视图处理 } [AcceptVerbs(HttpVerbs.Post)] //操作选择器,接受HttpGet请求 public ActionResult Create(User model) { //操作逻辑(略) //操作重定向到Index动作 return RedirectToAction("Index"); } } }
2.9.4 过滤器
ASP.NET MVC过滤器是加在Controller或Action上的一种属性(Attribute)。通过过滤器,ASP.NET MVC网站在处理用户请求时,可以处理一些附加的操作,如用户权限验证、系统日志、异常处理、缓存等。ASP.NET MVC框架中的过滤器有四个:ActionFilter(动作过滤器)、ResultFilter(结果过滤器)、AuthorizationFilter(授权过滤器)、ExceptionFilter(异常处理过滤器),每种异常的功能描述如表2-14所示。
表2-14 过滤器的功能描述
ASP.NET MVC中默认实现了相应功能的过滤器只有三种,分别是Authorize(授权)、ActionFilter(动作处理)和HandleError(错误处理),各种过滤器的功能实现描述如表2-15所示。
表2-15 默认控制器的功能描述
1.Action过滤器
Action过滤器是通过继承ActionFilterAttribute类来实现的一个Attribute类。ActionFilterAttribute是一个抽象类,提供了两个Virtual方法,即OnActionExecuting和OnActionExecuted用于重写。这两个方法的介绍如表2-16所示。
表2-16 Action过滤器的方法
ASP.NET MVC框架会在Action方法执行之前调用Action过滤器中的OnActionExecuting方法,在方法执行之后调用Action过滤器中的OnActionExecuted方法。当然在创建Action过滤器时不需要两个方法都实现,可以根据需要来创建。
以下示例是在调用Action方法之前和之后的日志跟踪代码:
public class LoggingFilterAttribute : ActionFilterAttribute { //Action方法执行之前 public override void OnActionExecuting(ActionExecutingContext filterContext) { //日志跟踪:记录Action开始执行 filterContext.HttpContext.Trace.Write("Starting: " + filterContext.ActionDescriptor.ActionName); } public override void OnActionExecuted(ActionExecutedContext filterContext) { if (filterContext.Exception ! =null) { //日志跟踪:记录Action已执行完毕 filterContext.HttpContext.Trace.Write("End:"+ filterContext.ActionDescriptor.ActionName); } } }
OnActionExecuting方法有一个类型为ActionExecutingContext的参数,而OnActionExecuted方法有一个类型为ActionExecutedContext的参数。两个Context类都是继承自FilterContext类,而FilterContext类继承自ControllerContext类并包含一个ActionMethod属性。可以使用ActionMethod属性来决定这个Action过滤器是应用到哪个Action方法上。
2.过滤器的使用
过滤器可以标记到任何一个Action方法上,也可以用来标记一个完成的控制器类,这样Action过滤器将会应用到该控制器的所有Action方法上。
如果一个控制器类继承自别的控制器类,则基控制器类可能有它自己的Action过滤器属性。如果你在子类中重写了基控制器类的Action方法,则子类的该Action方法也会有它自己的从基类继承而来的Action过滤器属性。
(1)添加指定Action的过滤器
以下代码展示了过滤器作用于Index()方法的用法:
public class HomeController : Controller { //使用LoggingFilter过滤器的Action [LoggingFilter] public ActionResult Index() { return View(); } //未使用过滤器的Action public void About() { return View(); } }
(2)添加控制器类的Action。以下代码展示了过滤器作用于HomeController类的使用方法:
//在控制器类上使用过滤器,LoggingFilter过滤器将作用于本类中的所有Action [LoggingFilter] public class HomeController : Controller { //使用LoggingFilter过滤器的Action public ActionResult Index() { return View(); } //使用LoggingFilter过滤器的Action public void About() { return View(); } }
(3)添加全局过滤器。默认情况下全局过滤器将作用于ASP.NET MVC应用程序中所有的Action,全局过滤器需要注册到\App_Start\FilterConfig.cs文件中,代码如下:
using System.Web; using System.Web.Mvc; using ZDSoft.LMS.Web.App_Start.Filter; namespace ZDSoft.LMS.Web { public class FilterConfig { public static void RegisterGlobalFilters (GlobalFilterCollection filters) { //添加日志过滤器 filters.Add(new LoggingFilterAttribute()); } } }
3.过滤器的执行顺序
每个Action都可能有多个过滤器,而每一个Action过滤器都有一个Order属性,用来决定Action过滤器在该范围内的执行顺序。Order属性必须是0(默认值)或者更大的整数值。省略Order属性,则会使该过滤器的Order值设为-1,表明未指明顺序。任何一个在同一范围的Action过滤器将Order设为-1,都会按不确定的顺序执行。
当设置Order属性值的时候,必需指定一个唯一的值。如果两个或者更多的Action过滤器具有相同的Order属性值,将会抛出一个异常。
假设动作的Index()方法有三个过滤器,并给每个过滤器都指定了Order属性Filter1(Order=2)、Filter2(Order=3)、Filter3(Order=1),则3个过滤器的执行顺序为:Filter3→Filter2→Filter1。代码如下:
[Filter1(Order =2)] [Filter2(Order =3)] [Filter3(Order =1)] public ActionResult Index() { return View(); }
4.几个常用的过滤器
(1)AcceptVerbs
它是一种特性过滤器,用于规定页面的请求方式,包括HttpVerbs.Get、HttpVerbs. Post、HttpVerbs.Delete、HttpVerbs.Head、HttpVerbs.Options、HttpVerbs.Put、HttpVerbs.Patch等参数,每个参数对应不同特性的过滤器,常用的参数有HttpVerbs. Get、HttpVerbs.Post。示例代码如下:
[AcceptVerbs(HttpVerbs.Post)] public ActionResult Example() { return View(); }
以上代码中,[AcceptVerbs(HttpVerbs.Post)]过滤器的功能是限制Example()动作只能以Post的方式访问。MVC框架中也提供了AcceptVerbs过滤器对应的其他过滤器,表2-17中列举了AcceptVerbs过滤器对应的其他过滤器。
表2-17 AcceptVerbs及对应的过滤器
AcceptVerbs过滤器和多数的过滤器在功能上基本相同,但在使用上也有区别,比如[HttpPost]和 [HttpGet]是不能同时作用于一个Action上,但 [AcceptVerbs(HttpVerbs.Get|HttpVerbs.Post)]可以被使用。
(2)ActionName
用于重新指定Action的访问名称。通常用于屏蔽Action的真实名称或替换真实名称为关键字的Action。UserController类中Login()方法使用ActionName过滤器的示例代码如下:
[ActionName("DengLu")] public ViewResult Login() { return View("Login"); }
以上代码中,在使用ActionName之前访问Login()的URL为:
http://主机名:端口/User/Login
使用ActionName之后,访问Login()的URL为:
http://主机名:端口/User/DengLu
(3)NonAction
使用NonAction过滤器后,Action方法将成为普通方法,而不会被MVC框架解析为Action。如:
[NonAction] public ActionResult Example() { return View(); }
(4)OutputCache
OutputCache过滤器用作缓存,节省用户访问应用程序的时间和资源,以提高用户的体验。OutputCacheAttribute类的主要属性如表2-18所示。
表2-18 OutputCacheAttribute类的主要属性
使用OutputCache过滤器的示例代码如下:
[OutputCache(Duration =60, VaryByParam ="id")] public ActionResult Example(int id) { return View(); }
以上代码为Example()方法设置了60秒缓存,当ID值发生变化时,系统会更新缓存的内容。
(5)ValidateInput
ASP.NET在执行Request.Form请求时可能会检测到包含潜在危险的数据,因为它可能包括HTML标记或脚本。该数据可能表示存在危及应用程序安全的尝试,如跨站点脚本攻击。可以在Action中使用过滤器明确指定是否需要执行潜在危险的Request.Form值检查。
ValidateInput过滤器类用于设置Action是否需要启用Request.Form潜在危险的检查,该类有一个布尔值的参数,当设置为ValidateInput(true)时,表示会执行潜在危险的检查;当设置为ValidateInput(false)时,不会执行潜在危险的检查。
如下代码表示不启用对Example()方法的Request.Form潜在危险的检查。
[ValidateInput(false))] public ActionResult Example(int id) { return View(); }
(6)ValidateAntiForgeryTokenAttribute
ValidateAntiForgeryTokenAttribute用于解决“跨站请求伪造”(CSRF, Cross-Site RequestForgery)”。这是一种不同于XSS(CrossSiteScript)的跨站网络攻击,如果说XSS是利用了用户对网站的信任,而CSRF就是利用了站点对认证用户的信任。ValidateAntiForgeryTokenAttribute则用于阻止伪造信任的请求。
[ValidateAntiForgeryToken] public ActionResult Example() { return View(); }
2.9.5 MVC中的页面传值
MVC中的页面传值,通常指Controller和View之间的数据传递,经常用到的有4种方式。
1.模型传值
所谓模型传值,即在控制器中把一个对象作为View方法的参数传递给视图,或者在视图中将强类型对象提交后传给控制器的动作方法。
(1)从控制器传到视图
控制器代码如下:
public ActionResult Create() { //实例化用户类 Models.User user =new Models.User(); //将对象作为View()方法的参数传递给视图 return View(); }
视图代码如下:
@model LMS.Models.User @{ Layout =null; } <! DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>创建用户</title> </head> <body> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>User</h4> <hr /> @Html.ValidationSummary(true) <div class="form-group"> @Html.LabelFor(model =>model.Name, new { @class ="control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model =>model.Name) @Html.ValidationMessageFor(model =>model.Name) </div> </div> <div class="form-group"> @Html.LabelFor(model =>model.Gender, new { @class ="control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model =>model.Gender) @Html.ValidationMessageFor(model =>model.Gender) </div> </div> <! --为节约篇幅,其他属性省略--> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn default" /> </div> </div> </div> </div> } </body> </html>
以上代码中通过强类型视图中的@modelLMS.Models.User方式接收从控制器传递过来的User模型的对象,并使用Razor语法绑定模型的属性。
(2)从视图传到控制器
单击强类型视图中的Submit类型元素,将视图中绑定的数据通过模型的方式提交到控制器中,控制器在接受此类数据时通常使用Post请求方式,且接受的参数为模型对象的数据类型。控制器中的代码如下:
[HttpPost] public ActionResult Create(Models.User user) { //处理接受对象的逻辑(略) return View(); }
2.ViewData
使用ViewData时采用键值对的形式,先获取或设置一个字典,并对所定义的数据进行传递。在View中会自动识别到拥有唯一键值的ViewData,并将数据显示出来,通常用于控制器向视图中传递代码。
控制器代码如下:
public ActionResult Index() { IList<Models.User>users =new List<Models.User>() { new Models.User(){ ID=1, Name="李老师", Gender='M'}, new Models.User(){ ID=2, Name="冷老师", Gender='M'}, new Models.User(){ ID=3, Name="张老师", Gender='F'} }; //使用ViewData将数据从控制器传到视图中 ViewData["Users"] =users; return View(); }
视图代码如下:
<table> <tr> <th>ID</th> <th>姓名</th> <th>性别</th> </tr> @foreach (var user in ViewData["Users"] as IList<LMS.Models.User>) { <tr> <td>@user.ID</td> <td>@user.Name</td> <td>@user.Gender</td> </tr> } </table>
以上视图代码中,接收来自于控制器的字典数据ViewData["Users"],并将其转换成IList<LMS.Models.User>类型,最后使用foreach语句循环输出每个用户的信息。
3.ViewBag
ViewBag属于动态类型(dynamic),使用自定义属性进行赋值,格式为:
ViewBag.属性=属性值
其功能与ViewData类似,通常用于控制器向视图中传递代码。
控制器代码如下:
public ActionResult Index() { IList<Models.User>users =new List<Models.User>() { new Models.User(){ ID=1, Name="李老师", Gender='M'}, new Models.User(){ ID=2, Name="冷老师", Gender='M'}, ew Models.User(){ ID=3, Name="张老师", Gender='F'} }; //使用ViewBag将数据从控制器传到视图中 ViewBag. Users =users; //自定义Users属性并进行赋值 return View(); }
视图代码如下:
<table> <tr> <th>ID</th> <th>姓名</th> <th>性别</th> </tr> @foreach (var user in ViewBag. Users as IList<LMS.Models.User>) { <tr> <td>@user.ID</td> <td>@user.Name</td> <td>@user.Gender</td> </tr> } </table>
以上视图代码中,接收来自于控制器的动态属性ViewBag.Users,并将其转换成IList<LMS.Models.User>类型,最后使用foreach语句循环输出每个用户的信息。
4.TempData
前面的传值方式都是在View和Controller之间进行数据传递。如果某一个业务功能需要用到控制器的两个Action,并且需要在这两个Action之间进行数据传递,这时需要用到TempData,它可以实现一次跨Action的数据保存,调用此数据的Action可以是当前的Action,也可以是其他的Action,只要TempData保存的对象被使用后,将立即置为null,具体的使用与ViewData相同,属于键值对的数据字典类。代码如下:
//提供TempData的源Action public ActionResult SourceAction() { //实例化User对象并用TempData保存,可在当前Action或其他Action中使用 TempData["User"]=new Models.User(){ID=1, Name="李老师", Gender='M'}; return View(); } //使用TempData的目标Action public ActionResult TargetAction() { //使用TempData字典数据中的User对象 Models.User user =TempData["User "]; //将对象以View方法传递到视图中 return Content(user); }