[转载]MVC中的扩展点(五)方法选择器 – xfrog – 博客园.
前一篇中我们介绍了过滤器,通过方法和结果过滤器我们可以在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);
.codearea { color: black; background-color: white; line-height: 18px; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,”BitStream Vera Sans Mono”,courier,monospace,serif; }.codearea pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap pre { white-space: pre-wrap; word-wrap: break-word; }.codearea pre.alt { background-color: rgb(247, 247, 255) ! important; }.codearea .lnum { color: rgb(79, 129, 189); line-height: 18px; }
按以上规则,如果我们请求的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);
} }.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; }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("通过其他浏览器访问"); } }.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; }
最后,选择器的功能似乎与授权过滤器类似:都可以”过滤”掉控制器方法,但是他们本质上是不同的:执行授权过滤器时,MVC已经确定调用哪个控制器方法, 而选择器是在MVC选择控制器方法时得一组过滤条件。如果通过选择器过滤后没有匹配方法,默认下会返回404错误,如果通过授权过滤器终止执行某个方法, 则返回的是你在通过授权过滤器上下文参数中指定的Result。