[转载]通过源代码研究ASP.NET MVC中的Controller和View(五) – Ivony… – 博客园.
通过源代码研究ASP.NET MVC中的Controller和View(一)
通过源代码研究ASP.NET MVC中的Controller和View(二)
通过源代码研究ASP.NET MVC中的Controller和View(三)
通过源代码研究ASP.NET MVC中的Controller和View(四)
第五篇,从这一篇开始,将研究ASP.NET的Controller,IController接口是这个样子的:
public interface IController
{
void Execute( RequestContext requestContext );
}
IController是控制器的抽象,由资料可 知,当ASP.NET MVC捕获到HTTP请求时,便会通过一系列的机制确定处理当前请求的Controller,创建IController的实例来处理这个请求 (RequestContext)。在IController之前的东西,其实是个Routing,或者说请求分发。具体的分发机制与ASP.NET Routing相关,不在我这一次的研究范畴。我们现在假设已经通过分发处理来到了IController,来看看IController的实例是如何处 理请求的。
首先通过Reflector看这个接口的实现情况:
很干净的继承链,没有什么旁系和分支,IAsyncController和AsyncController这两个类型从名称来看已经知道大体上应该 是用异步处理实现的Controller或IController(就像是IHttpAsyncHandler),不妨看看 IAsyncController接口长啥样:
public interface IAsyncController : IController
{
IAsyncResult BeginExecute( RequestContext requestContext, AsyncCallback callback, object state );
void EndExecute( IAsyncResult asyncResult );
}
显然事实就是这样,那么我们只需要关心同步处理的实现(Controller)便可以了,异步处理的逻辑不可能有很大的偏差。
按照一贯的传统,IController接口应该会被抽象基类ControllerBase实现,来看看:
#region IController Members void IController.Execute( RequestContext requestContext ) { Execute( requestContext ); } #endregion
protected virtual void Execute( RequestContext requestContext ) { if ( requestContext == null ) { throw new ArgumentNullException( "requestContext" ); } VerifyExecuteCalledOnce(); Initialize( requestContext ); ExecuteCore(); }
要说明一下这里兜了一个圈子,IController.Execute是一个显示接口实现,当我们将实例当作IController来调用时,会调用到这个方法,但旋即这个方法就调用了ControllerBase.Execute。那么来看Execute方法的实现。
VerifyExecuteCalledOnce,大意是验证Execute是否只被调用一次,一会儿来研究这个方法的实现。然后是初始化(Initialize),最后调用派生类的ExecuteCore方法(因为ExecuteCore是抽象方法)。
初始化的工作非常简单:
protected virtual void Initialize( RequestContext requestContext ) { ControllerContext = new ControllerContext( requestContext, this ); }
从这里也能看出,ControllerContext = RequestContext + ControllerBase
同时我发现Initialize方法是个虚的,看看派生类是否有篡改,果然:
protected override void Initialize( RequestContext requestContext ) { base.Initialize( requestContext ); Url = new UrlHelper( requestContext ); }
不过逻辑也非常简单,也只是创建了一个UrlHelper的实例。Execute方法虽然也是虚的,但是Controller并没有篡改,而是老老 实实的实现了ExecuteCore。这个一会儿再看,先来研究一下这个VerifyExecuteCalledOnce的实现。话说研究源代码的好处就 在于你可以收获许多研究结论之外的东西:
internal void VerifyExecuteCalledOnce() { if ( !_executeWasCalledGate.TryEnter() ) { string message = String.Format( CultureInfo.CurrentUICulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType() ); throw new InvalidOperationException( message ); } }
调用了一个TryEnter方法,从方法名来看,似乎是进入一个什么状态?临界区?暂时不清楚这个方法和只调用一次的逻辑有什么关系,继续查看源代码:
private readonly SingleEntryGate _executeWasCalledGate = new SingleEntryGate();
// used to synchronize access to a single-use consumable resource internal sealed class SingleEntryGate { private const int NOT_ENTERED = 0; private const int ENTERED = 1; private int _status; // returns true if this is the first call to TryEnter(), false otherwise public bool TryEnter() { int oldStatus = 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:
protected override void ExecuteCore() { // If code in this method needs to be updated, please also check the BeginExecuteCore() and // EndExecuteCore() methods of AsyncController to see if that code also must be updated. PossiblyLoadTempData(); try { string actionName = 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是一个属性:
public IActionInvoker ActionInvoker
{
get
{
if ( _actionInvoker == null )
{
_actionInvoker = CreateActionInvoker();
}
return _actionInvoker;
}
set
{
_actionInvoker = value;
}
}
protected virtual IActionInvoker CreateActionInvoker()
{
return new ControllerActionInvoker();
}
兜了一个圈子,我发现ActionInvoker属性的类型是IActionInvoker,而默认实例是一个ControllerActionInvoker类型的。
IActionInvoker只有一个方法:
public interface IActionInvoker
{
bool InvokeAction( ControllerContext controllerContext, string actionName );
}
那么职责显然是通过actionName调用Action,IActionInvoker的实现类型情况如下:
AsyncControllerActionInvoker和IAsyncActionInvoker应该是异步版本,那么看看ControllerActionInvoker的实现:
public virtual bool InvokeAction( ControllerContext controllerContext, string actionName )
{
if ( controllerContext == null )
{
throw new ArgumentNullException( "controllerContext" );
}
if ( String.IsNullOrEmpty( actionName ) )
{
throw new ArgumentException( MvcResources.Common_NullOrEmpty, "actionName" );
}
ControllerDescriptor controllerDescriptor = GetControllerDescriptor( controllerContext );
ActionDescriptor actionDescriptor = FindAction( controllerContext, controllerDescriptor, actionName );
if ( actionDescriptor != null )
{
FilterInfo filterInfo = GetFilters( controllerContext, actionDescriptor );
try
{
AuthorizationContext authContext = InvokeAuthorizationFilters( controllerContext, filterInfo.AuthorizationFilters, actionDescriptor );
if ( authContext.Result != null )
{
// the auth filter signaled that we should let it short-circuit the request
InvokeActionResult( controllerContext, authContext.Result );
}
else
{
if ( controllerContext.Controller.ValidateRequest )
{
ValidateRequest( controllerContext );
}
IDictionary<string, object> parameters = GetParameterValues( controllerContext, actionDescriptor );
ActionExecutedContext postActionContext = InvokeActionMethodWithFilters( controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters );
InvokeActionResultWithFilters( controllerContext, filterInfo.ResultFilters, postActionContext.Result );
}
}
catch ( ThreadAbortException )
{
// This type of exception occurs as a result of Response.Redirect(), but we special-case so that
// the filters don't see this as an error.
throw;
}
catch ( Exception ex )
{
// something blew up, so execute the exception filters
ExceptionContext exceptionContext = InvokeExceptionFilters( controllerContext, filterInfo.ExceptionFilters, ex );
if ( !exceptionContext.ExceptionHandled )
{
throw;
}
InvokeActionResult( controllerContext, exceptionContext.Result );
}
return true;
}
// notify controller that no method matched
return false;
}
好家伙,大量的代码都在这里了,我们慢慢来分析。
跳过一开始的入口检查,首先是获取两个Descriptor,ControllerDescriptor和ActionDescriptor,如果 ActionDescriptor是null,那么返回false,由于ActionDescriptor是由FindAction方法返回,结合调用方 的行为,有理由相信这里的逻辑是找不到Action的话就返回false,return false上方的注释也佐证了这一点。
然后从ActionDescriptor获取FilterInfo,从方法名GetFilters来看,FilterInfo应该是一个筛选器的集合。
紧接着进入一个try块,下面的catch逻辑首先是忽略ThreadAbortException(这个对于HTTP处理程序要说是必须的,因为 Response.End或Redirect就会产生这个异常),接着其他任何异常都会被捕获,然后InvokeExceptionFilters,这里 应该是异常筛选器(关于所有的Filter的内容,主线逻辑完成后我会来做一个总结)。如果异常没有被异常筛选器处理 (ExceptionHandled),那么继续抛出,否则InvokeActionResult(猜测这个方法就是调用 ActionResult.ExecuteResult)。
核实InvokeActionResult这个猜测很简单,看看源代码:
protected virtual void InvokeActionResult( ControllerContext controllerContext, ActionResult actionResult )
{
actionResult.ExecuteResult( controllerContext );
}
OK,枝节不继续深入,看try里面的情况,首先是调用授权筛选器(InvokeAuthorizationFilters),如果筛选器有结果(推测多半是授权失败之类),那么执行这个结果(InvokeActionResult)。
如果授权部分没有任何结果,那么看看Controller.ValidateRequest是不是true,决定是否进行ValidateRequest,这个ValidateRequest应该是检查XSS威胁之类的,实现如下:
internal static void ValidateRequest( ControllerContext controllerContext )
{
if ( controllerContext.IsChildAction )
{
return;
}
// DevDiv 214040: Enable Request Validation by default for all controller requests
//
// Note that we grab the Request's RawUrl to force it to be validated. Calling ValidateInput()
// doesn't actually validate anything. It just sets flags indicating that on the next usage of
// certain inputs that they should be validated. We special case RawUrl because the URL has already
// been consumed by routing and thus might contain dangerous data. By forcing the RawUrl to be
// re-read we're making sure that it gets validated by ASP.NET.
controllerContext.HttpContext.Request.ValidateInput();
string rawUrl = controllerContext.HttpContext.Request.RawUrl;
}
果然,HttpContext.Request.ValidateInput()。最后的那个rawUrl赋值并不是闲着蛋疼的,上面的注释说了这个原因,大意是:如果不获取RawUrl的值,那么请求验证其实不会真正的被执行,可以认为这是ASP.NET的一个Bug。
继续研究,ValidateRequest之后,调用GetParameterValues方法来获取一个IDictionary<string, object>,这个从名称上来看是获取参数。
然后InvokeActionMethodWithFilters,接着InvokeActionResultWithFilters
InvokeActionResultWithFilters看起来就是InvokeActionResult的WithFilters版本:
protected virtual ResultExecutedContext InvokeActionResultWithFilters( ControllerContext controllerContext, IList<IResultFilter> filters, ActionResult actionResult )
{
ResultExecutingContext preContext = new ResultExecutingContext( controllerContext, actionResult );
Func<ResultExecutedContext> continuation = delegate
{
InvokeActionResult( controllerContext, actionResult );
return new ResultExecutedContext( controllerContext, actionResult, false /* canceled */, null /* exception */);
};
// need to reverse the filter list because the continuations are built up backward
Func<ResultExecutedContext> thunk = filters.Reverse().Aggregate( continuation,
( next, filter ) => () => InvokeActionResultFilter( filter, preContext, next ) );
return thunk();
}
相当的复杂,但我们看到的确是调用了InvokeActionResult,其他的代码大体上是筛选期的逻辑,这些在以后再铺展来谈。我们还是看看 InvokeActionMethodWithFilters是不是也调用了InvokeActionMethod然后应用筛选器的逻辑:
protected virtual ActionExecutedContext InvokeActionMethodWithFilters( ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters )
{
ActionExecutingContext preContext = new ActionExecutingContext( controllerContext, actionDescriptor, parameters );
Func<ActionExecutedContext> continuation = () =>
new ActionExecutedContext( controllerContext, actionDescriptor, false /* canceled */, null /* exception */)
{
Result = InvokeActionMethod( controllerContext, actionDescriptor, parameters )
};
// need to reverse the filter list because the continuations are built up backward
Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate( continuation,
( next, filter ) => () => InvokeActionMethodFilter( filter, preContext, next ) );
return thunk();
}
这两个方法的代码几乎是如出一辙(似乎问到了DRY的味道)。好,暂且不管复杂的Filter逻辑,我们赶紧来总结一下ActionInvoker.InvokeAction的流程:
- 获取Controller的描述(Descriptor)
- 查找Action(FindAction)
- 如果找不到Action,那么返回false
- 获取所有的筛选器
- 进入try
- 调用授权筛选
- 如果授权筛选有结果,那么调用授权结果(猜测是授权失败之类)。
- 获取参数
- 调用ActionMethod(InvokeActionMethodWithFilters)
- InvokeActionMethod
- 调用ActionResult(InvokeActionResultWithFilters)
- InvokeActionResult
- ActionResult.ExecuteResult()
- InvokeActionResult
- 调用授权筛选
- 如果try块内有任何不是ThreadAbortedException的异常
- 调用异常筛选
如果我们把这些筛选的逻辑都去掉,则看起来像是这样:
- 查找Action(FindAction)
- 获取参数
- InvokeActionMethod
- InvokeActionResult
这里面 InvokeActionResult我们已经知道是干什么的了,而InvokeActionMethod从名称上来看应该是调用我们写在 Controller里面的被称之为Action的方法(例如HomeController.Index等),结合起来上面的 GetParameterValues方法就应该是获取这个方法的参数。