ASP.NET框架应用程序实战:软件开发工程师岗前必备
上QQ阅读APP看书,第一时间看更新

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);
        }