您的位置:首页 > 编程语言 > ASP

通过源代码研究ASP.NET MVC中的Controller和View(五)

2010-11-21 15:35 429 查看
通过源代码研究ASP.NETMVC中的Controller和View(一)

通过源代码研究ASP.NETMVC中的Controller和View(二)

通过源代码研究ASP.NETMVC中的Controller和View(三)

通过源代码研究ASP.NETMVC中的Controller和View(四)

第五篇,从这一篇开始,将研究ASP.NET的Controller,IController接口是这个样子的:

publicinterfaceIController
{
voidExecute(RequestContextrequestContext);
}

IController是控制器的抽象,由资料可知,当ASP.NETMVC捕获到HTTP请求时,便会通过一系列的机制确定处理当前请求的Controller,创建IController的实例来处理这个请求(RequestContext)。在IController之前的东西,其实是个Routing,或者说请求分发。具体的分发机制与ASP.NETRouting相关,不在我这一次的研究范畴。我们现在假设已经通过分发处理来到了IController,来看看IController的实例是如何处理请求的。

首先通过Reflector看这个接口的实现情况:





很干净的继承链,没有什么旁系和分支,IAsyncController和AsyncController这两个类型从名称来看已经知道大体上应该是用异步处理实现的Controller或IController(就像是IHttpAsyncHandler),不妨看看IAsyncController接口长啥样:

publicinterfaceIAsyncController:IController
{
IAsyncResultBeginExecute(RequestContextrequestContext,AsyncCallbackcallback,objectstate);
voidEndExecute(IAsyncResultasyncResult);
}

显然事实就是这样,那么我们只需要关心同步处理的实现(Controller)便可以了,异步处理的逻辑不可能有很大的偏差。

按照一贯的传统,IController接口应该会被抽象基类ControllerBase实现,来看看:

#regionIControllerMembers voidIController.Execute(RequestContextrequestContext) { Execute(requestContext); } #endregion
[/code]
[/code]
protectedvirtualvoidExecute(RequestContextrequestContext)
{
if(requestContext==null)
{
thrownewArgumentNullException("requestContext");
}

VerifyExecuteCalledOnce();
Initialize(requestContext);
ExecuteCore();
}

要说明一下这里兜了一个圈子,IController.Execute是一个显示接口实现,当我们将实例当作IController来调用时,会调用到这个方法,但旋即这个方法就调用了ControllerBase.Execute。那么来看Execute方法的实现。

VerifyExecuteCalledOnce,大意是验证Execute是否只被调用一次,一会儿来研究这个方法的实现。然后是初始化(Initialize),最后调用派生类的ExecuteCore方法(因为ExecuteCore是抽象方法)。

初始化的工作非常简单:

protectedvirtualvoidInitialize(RequestContextrequestContext)
{
ControllerContext=newControllerContext(requestContext,this);
}

从这里也能看出,ControllerContext=RequestContext+ControllerBase

同时我发现Initialize方法是个虚的,看看派生类是否有篡改,果然:

protectedoverridevoidInitialize(RequestContextrequestContext)
{
base.Initialize(requestContext);
Url=newUrlHelper(requestContext);
}

不过逻辑也非常简单,也只是创建了一个UrlHelper的实例。Execute方法虽然也是虚的,但是Controller并没有篡改,而是老老实实的实现了ExecuteCore。这个一会儿再看,先来研究一下这个VerifyExecuteCalledOnce的实现。话说研究源代码的好处就在于你可以收获许多研究结论之外的东西:

internalvoidVerifyExecuteCalledOnce()
{
if(!_executeWasCalledGate.TryEnter())
{
stringmessage=String.Format(CultureInfo.CurrentUICulture,MvcResources.ControllerBase_CannotHandleMultipleRequests,GetType());
thrownewInvalidOperationException(message);
}
}

调用了一个TryEnter方法,从方法名来看,似乎是进入一个什么状态?临界区?暂时不清楚这个方法和只调用一次的逻辑有什么关系,继续查看源代码:

privatereadonlySingleEntryGate_executeWasCalledGate=newSingleEntryGate();


//usedtosynchronizeaccesstoasingle-useconsumableresource
internalsealedclassSingleEntryGate
{

privateconstintNOT_ENTERED=0;
privateconstintENTERED=1;

privateint_status;

//returnstrueifthisisthefirstcalltoTryEnter(),falseotherwise
publicboolTryEnter()
{
intoldStatus=Interlocked.Exchange(ref_status,ENTERED);
return(oldStatus==NOT_ENTERED);
}

}

_executeCalledGate是一个SingleEntryGate的实例,SingleEntryGate的代码也一并列出了。从名称和代码基本上已经可以搞清楚是怎么一回事儿了。

这里的Interlocked.Exchange方法其实就是赋值,只不过是一个原子操作(就是说这个操作只有完成和未完成两种状态,不存在进行中状态),你可以简单的理解为这样的伪代码:

lock(_status)
{
oldStatus=_status;
_status=ENTERED;
}

当然这个代码是不正确的,因为值类型是不能被lock的,明白大体上是这个意思就行。

其实TryEnter方法上的注释已经写的非常明白了,意思是:如果TryEnter是第一次被调用,那么返回true,否则返回false。

当TryEnter方法第一次被调用时,oldStatus是_status没有被修改之前的默认值也就是0,而_status则会被修改为1(ENTERED),然后比较oldStatus和0(NOT_ENTERED)得到一个true的结果,从而实现这个功能。

那么ControllerBase的Execute逻辑已经清楚了,主要就干了两件事儿,确保Execute方法只被调用一次和准备ControllerContext,然后就把工作交给派生类的ExecuteCore:

protectedoverridevoidExecuteCore()
{
//Ifcodeinthismethodneedstobeupdated,pleasealsochecktheBeginExecuteCore()and
//EndExecuteCore()methodsofAsyncControllertoseeifthatcodealsomustbeupdated.

PossiblyLoadTempData();
try
{
stringactionName=RouteData.GetRequiredString("action");
if(!ActionInvoker.InvokeAction(ControllerContext,actionName))
{
HandleUnknownAction(actionName);
}
}
finally
{
PossiblySaveTempData();
}
}

方法一开头的注释大体上是告诉开发人员不要忘了还有BeginExecuteCore和EndExecuteCore这回事儿(如果这个方法的代码需要更新,也请检查AsyncController的Begin和EndExecuteCore方法,看看代码是否也必须更新)。

猜测一下,由于IAsyncController的入口不再是Execute,这样ExecuteCore也就不会被调用到,写在ExecuteCore里面的逻辑就应当被写到Begin和EndExecute中去。同样的,ControllerBase的Execute也不会被执行,这部分逻辑恐怕也要写在Begin和EndExecute里面,看了一下源代码,果然不出所料。因为源代码太长,也与今天的研究没啥关系。就不贴了。

看完了注释,接下来是尽可能的(?)加载TempData,最后又有一个尽可能的(?)保存TempData。暂时不明白这个Possibly是咩意思,但加载和保存临时数据还是能明白的,应该就是像ViewState一样的东西,这个与主线逻辑无关,暂时不去探究其实现。

然后是从路由数据中找出actionName,接着InvokeAction,如果返回false(我猜是找不到Action),则处理未知Action。

逻辑非常简单,可以看出来这里又把工作外包给了ActionInvoker去干,总结一下ExecuteCore的逻辑就是:

加载临时数据

调用action

保存临时数据

由于我要追溯的是主线逻辑,所以继续来看ActionInvoker.InvokeAction。ActionInvoker是一个属性:

publicIActionInvokerActionInvoker
{
get
{
if(_actionInvoker==null)
{
_actionInvoker=CreateActionInvoker();
}
return_actionInvoker;
}
set
{
_actionInvoker=value;
}
}

protectedvirtualIActionInvokerCreateActionInvoker()
{
returnnewControllerActionInvoker();
}

兜了一个圈子,我发现ActionInvoker属性的类型是IActionInvoker,而默认实例是一个ControllerActionInvoker类型的。

IActionInvoker只有一个方法:

publicinterfaceIActionInvoker
{
boolInvokeAction(ControllerContextcontrollerContext,stringactionName);
}

那么职责显然是通过actionName调用Action,IActionInvoker的实现类型情况如下:





AsyncControllerActionInvoker和IAsyncActionInvoker应该是异步版本,那么看看ControllerActionInvoker的实现:

publicvirtualboolInvokeAction(ControllerContextcontrollerContext,stringactionName)
{
if(controllerContext==null)
{
thrownewArgumentNullException("controllerContext");
}
if(String.IsNullOrEmpty(actionName))
{
thrownewArgumentException(MvcResources.Common_NullOrEmpty,"actionName");
}

ControllerDescriptorcontrollerDescriptor=GetControllerDescriptor(controllerContext);
ActionDescriptoractionDescriptor=FindAction(controllerContext,controllerDescriptor,actionName);
if(actionDescriptor!=null)
{
FilterInfofilterInfo=GetFilters(controllerContext,actionDescriptor);

try
{
AuthorizationContextauthContext=InvokeAuthorizationFilters(controllerContext,filterInfo.AuthorizationFilters,actionDescriptor);
if(authContext.Result!=null)
{
//theauthfiltersignaledthatweshouldletitshort-circuittherequest
InvokeActionResult(controllerContext,authContext.Result);
}
else
{
if(controllerContext.Controller.ValidateRequest)
{
ValidateRequest(controllerContext);
}

IDictionary<string,object>parameters=GetParameterValues(controllerContext,actionDescriptor);
ActionExecutedContextpostActionContext=InvokeActionMethodWithFilters(controllerContext,filterInfo.ActionFilters,actionDescriptor,parameters);
InvokeActionResultWithFilters(controllerContext,filterInfo.ResultFilters,postActionContext.Result);
}
}
catch(ThreadAbortException)
{
//ThistypeofexceptionoccursasaresultofResponse.Redirect(),butwespecial-casesothat
//thefiltersdon'tseethisasanerror.
throw;
}
catch(Exceptionex)
{
//somethingblewup,soexecutetheexceptionfilters
ExceptionContextexceptionContext=InvokeExceptionFilters(controllerContext,filterInfo.ExceptionFilters,ex);
if(!exceptionContext.ExceptionHandled)
{
throw;
}
InvokeActionResult(controllerContext,exceptionContext.Result);
}

returntrue;
}

//notifycontrollerthatnomethodmatched
returnfalse;
}

好家伙,大量的代码都在这里了,我们慢慢来分析。

跳过一开始的入口检查,首先是获取两个Descriptor,ControllerDescriptor和ActionDescriptor,如果ActionDescriptor是null,那么返回false,由于ActionDescriptor是由FindAction方法返回,结合调用方的行为,有理由相信这里的逻辑是找不到Action的话就返回false,returnfalse上方的注释也佐证了这一点。

然后从ActionDescriptor获取FilterInfo,从方法名GetFilters来看,FilterInfo应该是一个筛选器的集合。

紧接着进入一个try块,下面的catch逻辑首先是忽略ThreadAbortException(这个对于HTTP处理程序要说是必须的,因为Response.End或Redirect就会产生这个异常),接着其他任何异常都会被捕获,然后InvokeExceptionFilters,这里应该是异常筛选器(关于所有的Filter的内容,主线逻辑完成后我会来做一个总结)。如果异常没有被异常筛选器处理(ExceptionHandled),那么继续抛出,否则InvokeActionResult(猜测这个方法就是调用ActionResult.ExecuteResult)。

核实InvokeActionResult这个猜测很简单,看看源代码:

protectedvirtualvoidInvokeActionResult(ControllerContextcontrollerContext,ActionResultactionResult)
{
actionResult.ExecuteResult(controllerContext);
}

OK,枝节不继续深入,看try里面的情况,首先是调用授权筛选器(InvokeAuthorizationFilters),如果筛选器有结果(推测多半是授权失败之类),那么执行这个结果(InvokeActionResult)。

如果授权部分没有任何结果,那么看看Controller.ValidateRequest是不是true,决定是否进行ValidateRequest,这个ValidateRequest应该是检查XSS威胁之类的,实现如下:

internalstaticvoidValidateRequest(ControllerContextcontrollerContext)
{
if(controllerContext.IsChildAction)
{
return;
}

//DevDiv214040:EnableRequestValidationbydefaultforallcontrollerrequests
//
//NotethatwegrabtheRequest'sRawUrltoforceittobevalidated.CallingValidateInput()
//doesn'tactuallyvalidateanything.Itjustsetsflagsindicatingthatonthenextusageof
//certaininputsthattheyshouldbevalidated.WespecialcaseRawUrlbecausetheURLhasalready
//beenconsumedbyroutingandthusmightcontaindangerousdata.ByforcingtheRawUrltobe
//re-readwe'remakingsurethatitgetsvalidatedbyASP.NET.

controllerContext.HttpContext.Request.ValidateInput();
stringrawUrl=controllerContext.HttpContext.Request.RawUrl;
}

果然,HttpContext.Request.ValidateInput()。最后的那个rawUrl赋值并不是闲着蛋疼的,上面的注释说了这个原因,大意是:如果不获取RawUrl的值,那么请求验证其实不会真正的被执行,可以认为这是ASP.NET的一个Bug。

继续研究,ValidateRequest之后,调用GetParameterValues方法来获取一个IDictionary<string,object>,这个从名称上来看是获取参数。

然后InvokeActionMethodWithFilters,接着InvokeActionResultWithFilters

InvokeActionResultWithFilters看起来就是InvokeActionResult的WithFilters版本:

protectedvirtualResultExecutedContextInvokeActionResultWithFilters(ControllerContextcontrollerContext,IList<IResultFilter>filters,ActionResultactionResult)
{
ResultExecutingContextpreContext=newResultExecutingContext(controllerContext,actionResult);
Func<ResultExecutedContext>continuation=delegate
{
InvokeActionResult(controllerContext,actionResult);
returnnewResultExecutedContext(controllerContext,actionResult,false/*canceled*/,null/*exception*/);
};

//needtoreversethefilterlistbecausethecontinuationsarebuiltupbackward
Func<ResultExecutedContext>thunk=filters.Reverse().Aggregate(continuation,
(next,filter)=>()=>InvokeActionResultFilter(filter,preContext,next));
returnthunk();
}

相当的复杂,但我们看到的确是调用了InvokeActionResult,其他的代码大体上是筛选期的逻辑,这些在以后再铺展来谈。我们还是看看InvokeActionMethodWithFilters是不是也调用了InvokeActionMethod然后应用筛选器的逻辑:

protectedvirtualActionExecutedContextInvokeActionMethodWithFilters(ControllerContextcontrollerContext,IList<IActionFilter>filters,ActionDescriptoractionDescriptor,IDictionary<string,object>parameters)
{
ActionExecutingContextpreContext=newActionExecutingContext(controllerContext,actionDescriptor,parameters);
Func<ActionExecutedContext>continuation=()=>
newActionExecutedContext(controllerContext,actionDescriptor,false/*canceled*/,null/*exception*/)
{
Result=InvokeActionMethod(controllerContext,actionDescriptor,parameters)
};

//needtoreversethefilterlistbecausethecontinuationsarebuiltupbackward
Func<ActionExecutedContext>thunk=filters.Reverse().Aggregate(continuation,
(next,filter)=>()=>InvokeActionMethodFilter(filter,preContext,next));
returnthunk();
}

这两个方法的代码几乎是如出一辙(似乎问到了DRY的味道)。好,暂且不管复杂的Filter逻辑,我们赶紧来总结一下ActionInvoker.InvokeAction的流程:

获取Controller的描述(Descriptor)

查找Action(FindAction)

如果找不到Action,那么返回false

获取所有的筛选器

进入try

调用授权筛选

如果授权筛选有结果,那么调用授权结果(猜测是授权失败之类)。

获取参数

调用ActionMethod(InvokeActionMethodWithFilters)

InvokeActionMethod

调用ActionResult(InvokeActionResultWithFilters)

InvokeActionResult

ActionResult.ExecuteResult()

如果try块内有任何不是ThreadAbortedException的异常

调用异常筛选

如果我们把这些筛选的逻辑都去掉,则看起来像是这样:

查找Action(FindAction)

获取参数

InvokeActionMethod

InvokeActionResult

这里面InvokeActionResult我们已经知道是干什么的了,而InvokeActionMethod从名称上来看应该是调用我们写在Controller里面的被称之为Action的方法(例如HomeController.Index等),结合起来上面的GetParameterValues方法就应该是获取这个方法的参数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: