一.引言

好久没有写博客了,前一段时间学习了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);
}

                    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
{}

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

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

                    我们使用注册的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);
}
}

                    然后我们在Controllers目录下添加一个.cs 文件,并在该文件中定义两个同名的Controller类。如下面的代码片断所示,这两个HomeCotroller类分别定义在命名空间Artech.MvcApp和Artech.MvcApp.Controllers之中,而Index操作返回的是一个将Controller类型全名为内容的ContentResult对象。

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

                    现在我们直接运行该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");
}
}

                    对用同时匹配注册的路由规则的两个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");
}
}

                    再次运行我们的程序会在浏览器中得到如图3-3所示的结果,从中可以看出定义在命名空间“Artech.MvcApp”中的HomeController被最终选用,可见较之作为当前ControllerBuilder的默认命名空间,在路由注册过程中执行的命名空间具有更高的匹配优先级,前者可以视为后者的一种后备。

ASP.NET MVC Controller激活系统详解1的更多相关文章

  1. ASP.NET MVC Controller激活系统详解2

    一.引言 此篇博文紧接上篇博文进行阐述,本篇博文阐述的主题是Controller激活和url路由 二.总述 ASP.NET路由系统是HTTP请求抵达服务端的第一道屏障,它根据注册的路由规则对拦截的请求 ...

  2. ASP.NET MVC教程二:ASP.NET MVC应用程序结构详解

    在上一篇文章中,讲解了一些MVC的概念,并且创建了第一个ASP.NET MVC项目,这篇文章将讲解ASP.NET MVC程序中的代码解构,新创建的MVC应用程序解构如下图所示: 一.App_Data ...

  3. Attribute自定义特性+Asp.net MVC中的filter详解

    转载自:http://blog.csdn.net/wangyy130/article/details/44241957 一.filter简介 在了解自定义特性前,先引入一个概念filter,它是MVC ...

  4. asp.net mvc global.asax文件详解

    一.文件概述 global.asax这个文件包含全局应用程序事件的事件处理程序.它响应应用程序级别和会话级别事件的代码. 运行时, Global.asax 将被编译成一个动态生成的 .NET Fram ...

  5. 白话ASP.NET MVC之二:Controller激活系统的概览

    前文简介:我们抽象类路由规则的对象,RouteBase是路由对象的抽象基类,ASP.NET 的路由系统中有唯一一个从RouteBase继承的路由对象,那就是Route类型了.我们注册了路由对象Rout ...

  6. ASP.NET MVC 控制器激活(一)

    ASP.NET MVC 控制器激活(一) 前言 在路由的篇章中讲解了路由的作用,讲着讲着就到了控制器部分了,从本篇开始来讲解MVC中的控制器,控制器是怎么来的?MVC框架对它做了什么?以及前面有的篇幅 ...

  7. ASP.NET MVC 控制器激活(二)

    ASP.NET MVC 控制器激活(二) 前言 在之前的篇幅中,用文字和图像来表示了控制器的激活过程,描述的角度都是从框架默认实现的角度去进行描述的,这样也使得大家都可以清楚的知道激活的过程以及其中涉 ...

  8. Spring学习 6- Spring MVC (Spring MVC原理及配置详解)

    百度的面试官问:Web容器,Servlet容器,SpringMVC容器的区别: 我还写了个文章,说明web容器与servlet容器的联系,参考:servlet单实例多线程模式 这个文章有web容器与s ...

  9. 使用Code First建模自引用关系笔记 asp.net core上使用redis探索(1) asp.net mvc控制器激活全分析 语言入门必学的基础知识你还记得么? 反射

    使用Code First建模自引用关系笔记   原文链接 一.Has方法: A.HasRequired(a => a.B); HasOptional:前者包含后者一个实例或者为null HasR ...

随机推荐

  1. 2.Struts2配置文件

    1.配置文件的加载顺序 0. 需要掌握         * 加载了哪些个配置文件(重点的)         * 配置文件的名称是什么         * 配置文件的位置         * 配置文件的 ...

  2. GitHub不能访问问题

    在C:\Windows\System32\drivers\etc文件夹下HOSTS里面最后添加: 192.30.253.112 github.com 192.30.253.113 github.com ...

  3. 50. Set接口和Set的实现类HashSet

    集合分类:-------------------| Collection 单列集合的根接口   ---------------| List 如果实现了List接口的集合类,具备的特点是:有序,可重复- ...

  4. python编程学习day04

    1.函数名是变量名 “=”是内存指向,等号赋值操作,内存指向操作 变量——可赋值,可作为列表元素 函数名可以作为返回值返回 函数名可作为参数传递 2.闭包 内层函数使用了外层函数的变量 作用:可以让一 ...

  5. 使用Git 上传文件到云端(版本库)

    第一步:本地初始化Git版本库 git init 第二步:链接码云(云端) git remote add orgin "你的远程仓库地址"(复制链接后结尾是.git,如果没有记得加 ...

  6. HIVE的数据类型

  7. 移动端图片轮播效果:depth模式总结

    最近公司app改版首页增加了一处轮播图效果,但是跟普通的轮播效果不同,是类似于下图的样式,找了一些兼容移动端的插件以及jQuery源码,总结一下使用心得: 1:jquery源码:缺点是在手机端的滑动很 ...

  8. SXOI2018酱油记

    Day 0: 嗯前一天刚听说要去参加省选(可能以前也说了不过没听见),作为弱省高一的蒟蒻准备去打打酱油.下午去五中试机啥也没敲晃荡一圈又回去了.今年来也就是打打酱油心情自然是很平静,真不知道明年现在我 ...

  9. docker快速安装kibana

    一.拉取镜像 docker pull kibana:5.6.9 二.启动容器 docker run --name kibana -e ELASTICSEARCH_URL=http://10.0.0.1 ...

  10. 20180713NOIP模拟赛

    20180713NOIP模拟赛 T1:动物园 zoo.cpp 2s [题目描述] 给定一张图,点有点权,求每个点到其他所有点中所有点的权值最小值之和. [思路] \(50pts\)做法:对于每个点跑一 ...