前一篇中我们介绍了过滤器,通过方法和结果过滤器我们可以在MVC执行方法及结果的前后注入自己的功能,通过授权过滤器可以执行一些权限检查,阻止无权用户调用方法,通过异常过滤器处理方法执行过程中产生的异常。那么在执行方法之前,MVC又是如何确定使用何种控制器及其方法的呢?
我们已经知道,MVC使用DefaultControllerFactory控制器工厂来实例化控制器,其大致过程如下:
1、默认Route类的GetRouteData方法将按我们设定的Url规则解析当前请求的Url,并将Url规则中的给个参数存入RouteData.Values集合中。我们知道Mvc添加了一个默认的Route项:
routes.MapRoute("Default", // Route name"{controller}/{action}/{id}", // URL with parametersnew { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults);
按以上规则,如果我们请求的Url为:
http://localhost/News/GetNewsList
则对应的RouteData.Values[“controller”] = "News”; RouteData.Values[“action”] = "NewsList”
2、DefaultControllerFactory根据Route.Values[“controller”]确定实际的控制器类型,并实例化,如上例控制器工厂知道应实例化的控制器类型为NewsController。
3、通过控制器工厂返回的Controller对象的Execute方法,控制器通过一个实现了IActionInvoker接口的类(默认为ControllerActionInvoker类)使用RouteData.Values[“action”]值,确定具体运行控制器中的那一个方法。
4、执行控制器中的方法生成ActionResult
5、执行ActionResult.ExecuteActionResult生成最终应答内容。
在4、5步骤中涉及到我们上一章中介绍的过滤器,而方法选择器是在第3步骤中使用的,ControllerActionInvoker类中使用ActionMethodSelector类来获取与路由信息匹配的方法,具体执行过程如下图所示:
图中红色终点表示异常。ActionMethodSelector首先从控制器中非静态方法中获取方法名称等于RouteData.Values[“action”]或通过ActionName特性指定的名称等于RouteData.Values[“action”]的方法列表,其次依次调用方法列表中的每个方法上的选择器,去掉选择器返回为false的部分,如果最终由一个方法匹配则使用这个方法,如果没有方法匹配,则再检查方法列表中没有选择器的方法,如果存在一个,选中它,如果没有,则直接调用控制器的HandleUnknownAction方法,Controller中此方法默认返回一个404的HTTP错误。
下面我们来看MVC中实现的默认选择器类型:
ActionNameAttribute用于声明方法的别名,通常情况下使用ActionName将控制器中不同的方法映射为相同的控制器方法(相同的Url访问不同的方法)。它从抽象类ActionNameSelectorAttribute继承,你也可以从此类中继承实现自己的ActionName特性(不过似乎用处不大)。
ActionMethodSelectorAttribute抽象类是一些列选择器的基类,ActionMethodSelector选择方法时,会调用IsValidForRequest方法来检查当前方法是否有效。MVC实现了几个默认的选择器:HttpGet、HttpPost、HttpDelete、HttpPut以及AcceptVerbs用于检查当前请求的方式(匹配GET方法、POST方法等,AcceptVerbs用于匹配多个方法),事实上HttpGet等选择器是对AcceptVerbs的封装。另外一个选择器:NonAction表示当前方法不对外部请求公开(它的IsValidForRequest始终返回false)。如果我们需要实现自己的选择逻辑,则应从ActionMethodSelectorAttribute类继承。
以下示例实现一个选择器,将根据浏览器类型将相同的Url映射到不同的控制器方法:
1、创建一个空的MVC项目
2、实现BrowseSelectorAttribute
public class BrowseSelectorAttribute : ActionMethodSelectorAttribute { private string _userAgent = String.Empty; public BrowseSelectorAttribute(string userAgent) { _userAgent = userAgent; } public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo) {return controllerContext.HttpContext.Request.UserAgent.Contains(_userAgent);
} }3、创建HomeController控制器
public class HomeController : Controller { [ActionName("Index")] [BrowseSelector("MSIE")] public ActionResult IEIndex() { return Content("通过IE浏览器访问"); } [ActionName("Index")] [BrowseSelector("Chrome")] public ActionResult ChromeIndex() { return Content("通过Chrome浏览器访问"); } [ActionName("Index")] public ActionResult OtherIndex() { return Content("通过其他浏览器访问"); } }
最后,选择器的功能似乎与授权过滤器类似:都可以"过滤"掉控制器方法,但是他们本质上是不同的:执行授权过滤器时,MVC已经确定调用哪个控制器方法,而选择器是在MVC选择控制器方法时得一组过滤条件。如果通过选择器过滤后没有匹配方法,默认下会返回404错误,如果通过授权过滤器终止执行某个方法,则返回的是你在通过授权过滤器上下文参数中指定的Result。