ASP.NET MVC - 控制器(Controller)
IController接口
提供方法用于同步执行Controller
{
//同步执行控制器
voidExecute(RequestContextrequestContext);
}
IAsyncController接口
{
//异步执行控制器
IAsyncResultBeginExecute(RequestContextrequestContext,AsyncCallbackcallback,objectstate);
//结束异步执行
voidEndExecute(IAsyncResultasyncResult);
}
ControllerBase类(控制器基类)
此类是所有控制器的抽象基类,它显示实现了IController接口的Execute方法,在Execute的具体实现中会调用ControllerBase的Execute虚方法,在Execute虚方法中又会调用ControllerBase的Execute抽象方法,所以所有的控制器都必须实现抽象基类ControllerBase的Execute抽象方法才能执行控制器。通过ASP.NET MVC框架创建的控制器在执行完成后可能返回一个View(视图)给客户端(视开发者自己的需求而定),在控制器被初始化的时候,会同时初始化它的三个属性TempData(TempDataDictionary)、ViewData(ViewDataDictionary)、ViewBag(dynamic),这三个属性主要是用于向View传递一些数据。前两个是字典集合,后一个是动态类型,注意其中TempData的数据在读取一次后会被自动销毁。
//一个TempDataDictionary字典对象
ViewData
//一个ViewDataDictionary字典对象
ViewBag
//一个dymanic动态类型的对象
ControllerContext
//一个ControllerContext对象,此对象封装了具体的Controller对象和RequestContext对象
ControllerContext类(控制器上下文)
封装了Controller对象和RequestContext对象
Controller类(控制器抽象类)
从ControllerBase派生的控制器抽象类,所有通过Visual studio向导创建出的Controller都从Controller派生,此类继承和实现了如下的类型或接口
Controller实现了IAsyncController和IController接口,所以它既可以同步执行也可以异步执行。它的DisableAsyncSupport属性可以在派生类中重写,此属性如果被设为true则会禁止异步执行,默认是false。在其BeginExecute 方法中正是测试了DisableAsyncSupport的值,如果是false才会开启异步执行。
DefaultControllerFactory类(控制器工厂类)
DefaultControllerFactory实现了IControllerFactory接口,它有三个作用,1.根据路由地址中的控制器名称创建对应的控制器(CreateController方法)。2.获取默认的Session状态(GetControllerSessionBehavior方法)。3.控制器执行完毕后清理释放控制器(ReleaseController方法)。创建控制器是通过ControllerFactory的CreateController方法,该方法接收两个参数,requestContext和controllerName。
ControllerBuilder类(控制器生成器类)
用于创建IControllerFactory类型的实例。自带一个HashSet<string>类型的DefaultNamespaces属性,存储的是与所有控制器类型所在的命名空间。
Controller的激活过程
当Http模块拦截到Http请求后,如果请求的Url与路由表某个路由匹配成功,就会调用MvcRouteHandler处理请求,当MvcRouteHandler被初始化时,它会调用ControllerBuilder的静态属性Current创建一个各处可共享的具有唯一性的ControllerBuilder对象,然后由ControllerBuilder对象创建出IControllerFactory对象以便后者获取当前session的会话状态,最后,MvcRouteHandler会创建MvcHandler,MvcHandler就是最终处理Http请求的对象。
在MvcHandler的BeginProcessRequest方法中,首先利用ControllerBuilder.Current得到共享的ControllerBuilder对象,通过该ControllerBuilder创建出IControllerFactory,然后通过RequestContext获得当前客户端请求路由地址中的控制器的名称,然后将RequestContext和控制器名称作为参数传递给IControllerFactory的CreatController方法以便该创建出具体的控制器对象。(此处需要注意,如果定义了多个同名的控制器,那么CreatController方法将无法正确创建控制器实例,因为它不知道应该实例化哪一个控制器用于对请求的响应。为此,如果定义了多个同名的控制器则需要在通过RouteCollection的MapRoute方法注册路由时显示地以namespaces指出当前路由中控制器类所在的命名空间,指定了namespaces后,此属性会保存在当前路由的DataTokens字典集合中,通过Route.DataTokens[Namespaces]可以获取控制器所在的命名空间。)
接下来就是解析CreatController方法如何创建控制器了,如果仅仅凭传递给此方法的ControllerName,它是没有办法创建控制器的,因为路由地址中的控制器名称不区分大小写,所以假设控制器的类名是类似MYControllEr这样的格式,而请求的路由地址中的控制器名称是mycontroller,这样根本无法根据名称new出控制器对象,另外还要考虑一个情况,如果存在区域路由且注册区域路由时你没有显示指定路由的命名空间,那么区域路由的命名空间会默认带有.*后缀,因为控制器类名无法推测和区域路由的命名空间可能被带上了包含.*的后缀,所以不可能new出对象。最终这一解决方案是:DefaultControllerFactory将使用System.Web.Compilation.BuildManager.GetReferencedAssemblies方法载入当前项目引用的所有的程序集,利用反射查找所有的IController类型,将路由地址中的ControllerName拼接上Controller作后缀,然后在反射得到的控制器类型集合中过滤出控制器类型名与ControllerName完全相同的控制器,如果只有一个控制器则可以马上激活它,但因为可能过滤出不止一个控制器,所以还需要进一步按以下三种情况将得到的控制器所在的命名空间与反射得到的控制器类型所在的命名空间进行匹配以便确定用户请求的究竟是哪一个控制器
1.如果RouteData.DataTokens["Namespaces"]不是null,则将其与反射得到的控制器类型所在的命名空间进行匹配。匹配成则返回正确的控制器类型,否则返回null。
2.如果1是null,则测试RouteData.DataTokens["UseNamespaceFallback"]是否是null,如果不是,则说明用户没有禁用路由后续查找,所以可以获取ControllerBuilder的DefaultNamespaces,因为此属性所存储了所有控制器类型所在的命名空间,所以可以将其与反射得到的控制器类型所在的命名空间进行匹配,匹配成则返回正确的控制器类型,否则返回null。
3.如果1和2都是null,则将拼接了Controller作后缀的ControllerName字符与反射得到的控制器类型列表的每个控制器类型的名称进行匹配,如果得到的结果列表中只存在唯一能匹配的控制器类型则直接返回它,否则返回null。
自定义控制器激活类MyControllerFactory
{
publicclassMyControllerFactory:IControllerFactory
{
privatestaticList<Type>ControllerTypes=newList<Type>();
//构造函数
publicMyControllerFactory()
{
varDllList=System.Web.Compilation.BuildManager.GetReferencedAssemblies();
foreach(AssemblydllinDllList)
{
varIControllerClassList=dll.GetTypes().Where(t=>typeof(IController).IsAssignableFrom(t));
ControllerTypes.AddRange(IControllerClassList);
}
}
//requestedNamespace:有以下三种情况
//1.提供RouteData.DataTokens["Namespaces"]存储的当前路由所在的命名空间
//2.如果1是null,则测试RouteData.DataTokens["UseNamespaceFallback"]是否是false
//3.如果2是false,则说明用户没有禁用后续查找,
//targetNamesapce:反射得到的所有的控制器类型所在的命名空间
//此方法需要在迭代中调用以便完成对命名空间的解析
privatestaticboolIsNameSpaceMatch(stringrequestedNamespace,stringtargetNamesapce)
{
boolIsAreaNamespace=requestedNamespace.EndsWith(".*");
//如果是区域路由,则取消默认添加的".*"后缀
requestedNamespace=IsAreaNamespace?requestedNamespace.Substring(0,requestedNamespace.Length-2):requestedNamespace;
//返回两个命名空间的名字是否完全相同
returnstring.Equals(requestedNamespace,targetNamesapce);
}
//namespaces:待匹配的命名空间列表
//controllerTypes:控制器类型列表
//将控制器类型列表与待匹配的命名空间列表进行匹配
//返回匹配成功的控制器类型或null
privateTypeGetControllerType(IEnumerable<string>namespaces,IEnumerable<Type>controllerTypes)
{
vartypeList=controllerTypes.Where(type=>namespaces.Any(ns=>IsNameSpaceMatch(ns,type.Namespace)));
inttypeListCount=typeList.Count();
returntypeListCount==0?null:typeListCount>1?thrownewException("具有多个同名的Controller,无法确定使用哪一个控制器"):typeList.ToArray()[0];
}
publicTypeGetControllerType(RouteDatarouteData,stringControllerName)
{
//得到控制器
stringControllerTypeName=ControllerName+"Controller";//将ControllerName拼接上Controller作后缀
vartypes=ControllerTypes.Where(t=>ControllerTypeName.CompareTo(t.Name)==0);//在控制器的类型集合中过滤出控制器类型名与ControllerTypeName完全相同的控制器
if(types.Count()==0){returnnull;}
//考虑到可能会过滤出多个同名的控制器,所以还需要进一步将路由信息中存储的路由所在的命名空间取出来与控制器所在的命名空间进行匹配
//从而正确得到一个与Http请求地址所对应的控制器
varnamesapces=routeData.DataTokens["Namespaces"]==null?newstring[0]:routeData.DataTokens["Namespaces"]asstring[];//获取当前路由信息中的命名空间
TypecontrollerType=GetControllerType(namesapces,types);
if(controllerType!=null){returncontrollerType;}
//如果路由信息并未存储Namespaces(注册路由时可能没有显示地指定namespaces),则判断RouteData.DataTokens["UseNamespaceFallback"]是否允许后续查找
//如果允许,则继续后续查找,通过ControllerBuilder.Current.DefaultNamespaces获取到所有的控制器类型所在的命名空间列表,然后继续进行匹配
boolIsFallBack=routeData.DataTokens["UseNamespaceFallback"]!=null?(bool)routeData.DataTokens["UseNamespaceFallback"]:false;
if(!IsFallBack){returnnull;}
controllerType=GetControllerType(ControllerBuilder.Current.DefaultNamespaces,types);
if(controllerType!=null){returncontrollerType;}
//如果以上条件都只能得到null,则判断types是否只存在一个与ControllerName匹配的控制器,如果是则返回它
inttypesCount=types.Count();
if(typesCount>1){thrownewException("具有多个同名的Controller,无法确定使用哪一个控制器");}
elseif(typesCount==0){returnnull;}
returntypes.ToArray()[0];
}
//创建控制器
publicIControllerCreateController(RequestContextrequestContext,stringcontrollerName)
{
TypecontrollerType=GetControllerType(requestContext.RouteData,controllerName);//获取控制器的类型
if(controllerType==null){returnnull;}
return(IController)Activator.CreateInstance(controllerType);//实例化控制器
}
//获取Session会话状态
publicSessionStateBehaviorGetControllerSessionBehavior(RequestContextrequestContext,stringcontrollerName)
{
TypecontrollerType=GetControllerType(requestContext.RouteData,controllerName);
if(null==controllerType)
{
returnSessionStateBehavior.Default;
}
SessionStateAttributeattribute=controllerType.GetCustomAttributes(true).OfType<SessionStateAttribute>().FirstOrDefault();
attribute=attribute??newSessionStateAttribute(SessionStateBehavior.Default);
returnattribute.Behavior;
}
//释放控制器
publicvoidReleaseController(IControllercontroller)
{
IDisposabledisposable=controllerasIDisposable;
if(null!=disposable)
{
disposable.Dispose();
}
}
}
}
完成以上对自定义控制器工厂的创建后,还需要在Global文件中的Application_Start中设置默认的控制器工厂,以便ControllerBuilder能正确创建出自定义的控制器工厂的实例。
{
protectedvoidApplication_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ControllerBuilder.Current.SetControllerFactory(newMyControllerFactory());//动态将默认的DefaultControllerFactory替换掉
}
}
最后在任意控制器的Action中做测试:
{
ViewBag.msg="本控制器由自定义的MyControllerFactory激活";
returnView();
}
通过以上的激活流程,在MvcHandler的BeginProcessRequest方法中就能得到正确控制器类型并new出控制器对象了。接着会调用控制器对象的Execute或BeginExecute方法(同步或异步)执行控制器,执行完毕后,MvcHandler再调用IControllerFactory对象的ReleaseController方法对控制器对象进行释放清理操作。至此,Http请求如何被路由到控制器中,MvcHandler又是如何处理Http请求的,这整套业务流程通过ASP.NET MVC - 路由(最后部分的分析)和上面的代码解析就彻底清晰明朗了。,
ASP.NET MVC - 学习总目录
ASP.NET MVC - 控制器
原文地址:http://www.cnblogs.com/myrocknroll/p/7622947.html