一. 引言
好久没有写博客了, 前一段时间学习了 Controller 激活的一篇很好的博文(链接), 在此做个学习总结.
二. Controller
2.1 IController
Controller 类型直接或间接实现了 IController 接口. 当一个 Controller 对象被激活之后, 核心的操作就是根据请求上下文解析出目标 Action 方法, 并通过 Model 绑定机制从请求上下文中提取相应的数据映射为方法的参数并最终执行 Action 方法. 所有的这些操作都是调用这个 Execute 方法来执行的.
- public interface IController
- {
- void Execute(RequestContext requestContext);
- }
- 2.2 ControllerBase
抽象类 ControllerBase 实现了 IController 接口.
- public abstract class ControllerBase:IController
- {
- public ControllerContext ControllerContext {get;set;}
- public TempDataDictionary {get;set;}
- public object ValueBag {get;set;}
- public ViewDataDictionary {get;set;}
- }
从上面代码可以看出 ControllerBase 具备以下几个属性: ControllerContext,TempDataDictionary,ValueBag,ViewDataDictionary.
TempDataDictionary,ValueBag,ViewDataDictionary 用于存储从 Controller 向 View 传递的数据或变量.
2.3 ControllerContext
在 MVC 中我们遇到了一系列的上下文 (Context) 对象, 在我的其他博文中对 RequestContext 进行了介绍, RequestContext 包含 HttpContext 和 RouteData 两个属性.
- public class ControllerContext
- {
- public ControllerContext(){}
- public ControllerContext(RequestContext requestContext,ControllerBase controllerBase);
- public ControllerContext(HttpContextBase httpContext,RouteData routeData,ControllerBase controllerBase);
- public virtual ControllerBase Controller {get;set;}
- public RequestContext RequestContext {get;set;}
- public virtual HttpContextBase HttpContext {get;set;}
- public virtual RouteData {get;set;}
ControllerContext 就是基于某个 Controller 对象的上下文, ControllerContext 是实际是对一个 Controller 对象和 RequestContext 对象的封装, 这两个对象对象分别对应着定义在 ControllerContext 的同名属性, 并且可以在构造函数中初始化, 并且可以在构造函数中被初始化. 通过属性 HttpContext 和 RouteData 属性返回的 HttpContextBase 和 RouteData 对象在默认情况下实际就是组成 RequestContext 的核心元素. 这四个属性都是可读可写, 当 ControllerBase 的 Execute 方法被执行的手, 它会根据传入的 RequestContext 创建 ControllerContext 对象.
2.4 ControllerFactory
MVC 为 Controller 的激活定义了相应的工厂, 我们将其称为 ControllerFactory, 所有的 ControllerFactory 都实现了 IControllerFactory 接口. Controller 对象的激活都是通过 IControllerFactory 的 CreateController 方法来完成的.
- public interface IControllerFactory
- {
- IController CreateController(RequestContext context,string controllerName);
- SessionStateBehavior GetControllerSessionBehavior(RequestContext request,string controllerName);
- void ReleaseController(IController controller);
- }
- View Code
CreateFactory 方法来完成 Controller 对象的激活, 该方法的两个参数分别表示当前请求上下文和从路由信息中获取的 Controller 的名称(最初来源于请求地址).
处理负责创建 Controller 请求之前, ControllerFactory 还需要在完成请求处理之后对 Controller 的释放回收, 回收的处理是在 ReleaseController 方法中. 枚举 SessionStateBehavior 有四个项: Default,Required,Readonly,Disabled 四个项. 分别解释为:
Default: 使用默认 ASP.NET 逻辑来确定请求的会话状态行为.
Required: 为请求启用完全的读写会话状态行为.
Readonly: 为请求启用只读会话状态.
Disabled: 禁用会话状态
对于 Default 来说, ASP.NET 通过映射的 HttpHandler 类型是否实现了相关接口来决定具体的会话状态控制. 在 System.web.SessionState 命名空间下定义了 IRequestSessionState 和 IReadOnlySessionState 接口. 如下:
- public interface IRequestSessionState
- {}
- public interface IReadOnlySessionState:IRequestSessionState
- {}
- View Code
如果 HttpHandler 实现了接口 IRquestSessionState, 则意味着采用 Readonly 模式, 如果只实现了 IRequestSessionState 则采用 Required 模式.
具体采用何种会话状态行为取决于当前 Http 上下文(HttpContext.Current).
ASP.NET4.0 为 HttpContext 定义了一个 SetSessionStateBehavior 方法, 我们可以通过这个方法实现自由选择会话状态行为模式. HttpContextBase 的子类 HttpContextWrapper 重写了这个方法.
- public sealed class HttpContext:IServiceProvider,IPrincipalContainer
- {
- public void SetSessionStateBehavior(SessionStateBehavior session);
- }
- public class HttpContextBase:IServiceProvider
- {
- public void SetSessionStateBehavior(SessionStateBehavior session);
- }
- View Code
- 2.5 ControllerBase
用于激活 Controller 对象的 ControllerFactory 最终通过 ControllerBuilder 注册到 MVC 中, 如下面代码所示, ControllerBuilder 定义了一个静态只读属性 Current 用于返回当前的 ControllerBuilder 对象, 这是针对整个 web 应用的全局对象. 两个 SetControllerFactory 方法重载用于注册 ControllerFactory 的类型或实例, 而 GetControllerFactory 则返回一个具体的 ControllerFactory 对象.
- public class ControllerBuilder
- {
- public IControllerFactory GetControllerFactory();
- public void SetControllerFactory(Type controllerFactoryType);
- public void SetControllerFactory(IControllerFactory controllerFactory);
- public HashSet<string> DefaultNamespace{get;}
- public static ControllerFactory Current{get;}
- }
- View Code
我们使用注册的 ControllerFactory 的类型, 那么 GetControllerFactory 在执行的时候会通过对注册类型的反射 (调用 Activator 的静态方法 CreateInstance) 来创建具体的 ControllerFactory. 如果注册的是一个具体的 ControllerFactory 对象, 该对象直接从 GetControllerFactory 返回.
被 ASP.NET 路由系统进行拦截处理后生产一个用于封装路由信息的 RouteData 对象, 而目标 Controller 的名称就包含在通过该 RouteData 的 Values 属性表示的 RouteValueDictionary 对象中对应的 Key 为 "controller". 在默认情况下, 这个作为路由数据的名称只能帮我们解析出 Controller 的类型名称, 如果我们在不同的命名空间下定义了多个同名的 Controller 类, 会导致激活系统无法确定具体的 Controller 的类型从而抛出异常.
为解决这个问题, 我们必须为定义同名的 Controller 类型的命名空间设置不同的优先级, 具体说我们有两种提升命名空间优先级的方式:
1. 在调用 ROuteCollection 的扩展方法 MapRoute 时, 指定一个命名空间的列表.. 通过这种方式指定的命名空间列表会保存在 Route 对象的 DataTokens 属性表示的 RouteValueDictionary 字典中, 对应的 Key 为 "Namespace".
2. 将其添加到当前的 ControllerBuilder 中的默认命名空间列表中, 从上面的 ControllerBuilder 的定义可以看出, 他具有一个 HashSet<string > 类型的只读属性 DefaultNamespaces 就代表 这么一个默认命名空间列表.
用于辅助解析 Controller 类新的命名空间分为三个梯队, 简称为路由命名空间, ConrollerBuilder 命名空间和 Controller 类型命名空间. 如果一个梯队不能正确解析出目标 Controller 的类型, 会将后面一个梯队的命名空间作为后备.
为了让读者对此如何提升命名空间优先级具有一个深刻的印象, 我们来进行一个简单的实例演示. 我们使用 Visual Studio 提供的项目模板创建一个空的 ASP.NET MVC 应用, 并且使用如下所示的默认路由注册代码.
- public class MvcApplication : System.Web.HttpApplication
- {
- public static void RegisterRoutes(RouteCollection routes)
- {
- routes.MapRoute(
- name: "Default",
- url: "{controller}/{action}/{id}",
- defaults: new { controller = "Home", action = "Index",
- id = UrlParameter.Optional }
- );
- }
- protected void Application_Start()
- {
- // 其他操作
- RegisterRoutes(RouteTable.Routes);
- }
- }
- public class MvcApplication : System.Web.HttpApplication
- {
- public static void RegisterRoutes(RouteCollection routes)
- {
- routes.MapRoute(
- name: "Default",
- url: "{controller}/{action}/{id}",
- defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
- );
- }
- protected void Application_Start()
- {
- // 其他操作
- RegisterRoutes(RouteTable.Routes);
- }
- }
- View Code
然后我们在 Controllers 目录下添加一个. cs 文件, 并在该文件中定义两个同名的 Controller 类. 如下面的代码片断所示, 这两个 HomeCotroller 类分别定义在命名空间 Artech.MvcApp 和 Artech.MvcApp.Controllers 之中, 而 Index 操作返回的是一个将 Controller 类型全名为内容的 ContentResult http://msdn2.microsoft.com/dd460365.aspx 对象.
- namespace Artech.MvcApp.Controllers
- {
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- return this.Content(this.GetType().FullName);
- }
- }
- }
- namespace Artech.MvcApp
- {
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- return this.Content(this.GetType().FullName);
- }
- }
- }
- View Code
现在我们直接运行该 Web 应用. 由于具有多个 Controller 与注册的路由规则相匹配导致 ASP.NET MVC 的 Controller 激活系统无法确定目标哪个类型的 Controller 应该被选用, 所以会出现如下图所示的错误.
目前定义了 HomeController 的两个命名空间具有相同的优先级, 现在我们将其中一个定义在当前 ControllerBuilder 的默认命名空间列表中以提升匹配优先级. 如下面的代码片断所示, 在 Global.asax 的 Application_Start 方法中, 我们将命名空间 "Artech.MvcApp.Controllers" 添加到当前 ControllerBuilder 的 DefaultNamespaces 属性所示的命名空间列表中.
- public class MvcApplication : System.Web.HttpApplication
- {
- protected void Application_Start()
- {
- // 其他操作
- ControllerBuilder.Current.DefaultNamespaces.Add("Artech.MvcApp.Controllers");
- }
- }
- View Code
对用同时匹配注册的路由规则的两个 HomeController, 由于 "Artech.MvcApp.Controllers" 命名空间具有更高的匹配优先级, 所有定义其中的 HomeController 会被选用, 这可以通过如下图所示的运行结果看出来.
为了检验在路由注册时指定的命名空间和作为当前 ControllerBuilder 的命名空间哪个具有更高匹配优先级, 我们修改定义在 Global.asax 中的路由注册代码. 如下面的代码片断所示, 我们在调用 RouteTable 的静态属性 Routes 的 MapRoute 方法进行路由注册的时候指定了命名空间("Artech.MvcApp").
- public class MvcApplication : System.Web.HttpApplication
- {
- public static void RegisterRoutes(RouteCollection routes)
- {
- routes.MapRoute(
- name: "Default",
- url: "{controller}/{action}/{id}",
- defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
- namespaces:new string[]{"Artech.MvcApp"}
- );
- }
- protected void Application_Start()
- {
- // 其他操作
- RegisterRoutes(RouteTable.Routes);
- ControllerBuilder.Current.DefaultNamespaces.Add("Artech.MvcApp.Controllers");
- }
- }
- View Code
再次运行我们的程序会在浏览器中得到如图 3-3 所示的结果, 从中可以看出定义在命名空间 "Artech.MvcApp" 中的 HomeController 被最终选用, 可见较之作为当前 ControllerBuilder 的默认命名空间, 在路由注册过程中执行的命名空间具有更高的匹配优先级, 前者可以视为后者的一种后备.
来源: https://www.cnblogs.com/xiaowangzi1987/p/9333422.html