MVC 源码系列之控制器执行(一)
控制器的执行
之前说了Controller的激活,现在看一个激活Controller之后控制器内部的主要实现。
public interface IController
{
void Execute(RequestContext requestContext);
}
先看一下IController的接口,就一个方法Execute的方法,参数为RequestConext。
graph TD
IController-->ControllerBase
ControllerBase-->Controller
上图是Controller的简单的继承关系。
IContrller接口的Execute被ControllerBase实现了。具体实现的代码如下。
void IController.Execute(RequestContext requestContext)
{
Execute(requestContext);
}
protected virtual void Execute(RequestContext requestContext)
{
if (requestContext == null)
{
throw new ArgumentNullException("requestContext");
}
if (requestContext.HttpContext == null)
{
throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
}
VerifyExecuteCalledOnce();
Initialize(requestContext);
using (ScopeStorage.CreateTransientScope())
{
ExecuteCore();
}
}
首先显示的实现了IController的接口,然后调用本身的Execute的方法。VerifyExecuteCalledOnce()看方法名的意思应该是验证是否是第一次执行。这个后面细说。然后调用了Initialize().
protected virtual void Initialize(RequestContext requestContext)
{
ControllerContext = new ControllerContext(requestContext, this);
}
很简单就是将请求的上下文和Controller做个了绑定。但是方法是一个虚方法,看看在子类里面有没有重写?
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
Url = new UrlHelper(requestContext);
}
重写了,对UrlHelper进行了一个初始化,应该是一个Url的工具类。然后useing了一个作用域?也不知道拿来干嘛,就调用了子类(Controller)里面的ExecuteCore().整个逻辑应该差不多了。回头来看看VerifyExecuteCalledOnce的具体实现。
private readonly SingleEntryGate _executeWasCalledGate = new SingleEntryGate();
internal void VerifyExecuteCalledOnce()
{
if (!_executeWasCalledGate.TryEnter())
{
string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType());
throw new InvalidOperationException(message);
}
}
调用了_executeWasCalledGate属性的TryEnter()方法,如果放回为false就报错。而executeWasCalledGate是SingleEntryGate的实例。看看SingleEntryGate的结构吧。
internal sealed class SingleEntryGate
{
private const int NotEntered = 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 == NotEntered);
}
}
简单说一下,其实Interlocked.Exchange是一个原子的赋值操作。方法返回的应该是_status的初始值
- 第一次调用TryEnter [Enter=1,_status=0,NotEntered=0,oldStatus=0] oldStatus == NotEntered 返回true
- 第二次调用TryEnter [Enter=1,_status=1,NotEntered=0,oldStatus=1] oldStatus == NotEntered 返回false
- 第一个调用的oldStatus返回的0 第二次是1
所以就是简单的第一次给_status赋值时,返回的为0,赋值也完成了_state的值就变成了1,然后Exchage一直返回1,整个方法就返回false了。简单是 只有第一次的是会返回true,第二次便会返回false。
ControllerBase的逻辑说玩了,简单的说就做了三件事:
- 判断请求是否第一次进来
- 初始化ControllerContext
- 交给子类的ExecuteCore方法中去
现在看一下Controller里面的实现:
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 = GetActionName(RouteData);
if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
{
HandleUnknownAction(actionName);
}
}
finally
{
PossiblySaveTempData();
}
}
PossiblyLoadTempData 看方法名应该是加载TempData.然后获取到actionName,调用ActionInvoker.InvokeAction如果返回false的话,就抛出错误。然后保存TempData?
- 加载TempData
- 获取Action的名字
- 执行Action
- 保存TempataData
还是把 PossiblyLoadTempData和PossiblySaveTempData 单独拿出来看看里面的实现细节:
internal void PossiblyLoadTempData()
{
if (!ControllerContext.IsChildAction)
{
TempData.Load(ControllerContext, TempDataProvider);
}
}
internal void PossiblySaveTempData()
{
if (!ControllerContext.IsChildAction)
{
TempData.Save(ControllerContext, TempDataProvider);
}
}
可以看到,首先判断是否不是子的Action,然后调用了TempData的Load和Save.重点看的是TempData和TempDateProvider这两个。
一个是方法的拥有者,一个是方法的参数。
public TempDataDictionary TempData
{
get
{
if (ControllerContext != null && ControllerContext.IsChildAction)
{
return ControllerContext.ParentActionViewContext.TempData;
}
if (_tempDataDictionary == null)
{
_tempDataDictionary = new TempDataDictionary();
}
return _tempDataDictionary;
}
set { _tempDataDictionary = value; }
}
F12,就可以看到代码的源码了。 ControllerContext.IsChildAction在这个执行环境中为false,所以不会到第一个if里面去。所以是第二个if,TempData的类型也就出来了是TempDataDictionary。这边也可以看出如果是ChildAction会调用父级的TempData。
public class TempDataDictionary : IDictionary<string, object>
{
private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public object this[string key]
{
get
{
object value;
if (TryGetValue(key, out value))
{
_initialKeys.Remove(key);
return value;
}
return null;
}
set
{
_data[key] = value;
_initialKeys.Add(key);
}
}
public void Keep()
{
_retainedKeys.Clear();
_retainedKeys.UnionWith(_data.Keys);
}
public void Keep(string key)
{
_retainedKeys.Add(key);
}
public object Peek(string key)
{
object value;
_data.TryGetValue(key, out value);
return value;
}
public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
{
IDictionary<string, object> providerDictionary = tempDataProvider.LoadTempData(controllerContext);
_data = (providerDictionary != null)
? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase)
: new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
_initialKeys = new HashSet<string>(_data.Keys, StringComparer.OrdinalIgnoreCase);
_retainedKeys.Clear();
}
public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
{
// Frequently called so ensure delegate is stateless
_data.RemoveFromDictionary((KeyValuePair<string, object> entry, TempDataDictionary tempData) =>
{
string key = entry.Key;
return !tempData._initialKeys.Contains(key)
&& !tempData._retainedKeys.Contains(key);
}, this);
tempDataProvider.SaveTempData(controllerContext, _data);
}
}
TempDataDictionary本身继承了字典类型。首先Load,代码的第一句就获取了tempDataProvider.LoadTempData获得providerDictionary,然后将获得的数组都忽略大小写。然后给_initialKeys赋值。对_retainedKeys进行clear()。_initialKeys和_retainedKeys这两个都是一个HashSet的集合。这两个集合可以决定在Save的方法的时候数据要不要保存到session里面。看到save里面如果key不存在_initialKeys和_retainedKeys两个数组里面的话便删除。对着两个数组进行操作的方法有三个:集合正常的取值,Keep和Peek。
- 正常的取值的话会将数据的从initialKeys移除,如果没有调用Keep和peek的话 就会从session中去掉(也就是被使用了)
- 调用keep可以将单个或者所有添加的TempData保存到下一次的Session中
- 调用peek可以取值,然后将值保存到下一个请求中去。
然后来看看这个参数。
public ITempDataProvider TempDataProvider
{
get
{
if (_tempDataProvider == null)
{
_tempDataProvider = CreateTempDataProvider();
}
return _tempDataProvider;
}
set { _tempDataProvider = value; }
}
protected virtual ITempDataProvider CreateTempDataProvider()
{
return Resolver.GetService<ITempDataProvider>() ?? new SessionStateTempDataProvider();
}
看到Resolver我们应该很熟悉了,可以通过自定义或者第三方的类去注册到MVC里面,对TempData进行加载和保存。基本会走默认的类型。所以实际上调用的类型是SessionStateTempDataProvider。
来看看SessionStateTempDataProvider的方法。
public class SessionStateTempDataProvider : ITempDataProvider
{
internal const string TempDataSessionStateKey = "__ControllerTempData";
public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
{
HttpSessionStateBase session = controllerContext.HttpContext.Session;
if (session != null)
{
Dictionary<string, object> tempDataDictionary = session[TempDataSessionStateKey] as Dictionary<string, object>;
if (tempDataDictionary != null)
{
// If we got it from Session, remove it so that no other request gets it
session.Remove(TempDataSessionStateKey);
return tempDataDictionary;
}
}
return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
HttpSessionStateBase session = controllerContext.HttpContext.Session;
bool isDirty = (values != null && values.Count > 0);
if (session == null)
{
if (isDirty)
{
throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
}
}
else
{
if (isDirty)
{
session[TempDataSessionStateKey] = values;
}
else
{
// Since the default implementation of Remove() (from SessionStateItemCollection) dirties the
// collection, we shouldn't call it unless we really do need to remove the existing key.
if (session[TempDataSessionStateKey] != null)
{
session.Remove(TempDataSessionStateKey);
}
}
}
}
}
主要就两个方法LoadTempData和SaveTempData也分别在前面的方法中被调用了。首先看LoadTempData。获得请求的Session,看其中有没有__ControllerTempData为key的session。如果有的话返回,并将它在session中删除。注释是这样解释的,防止其他的请求获得这个session。简单的逻辑。
- 获得session
- 配备__ControllerTempData的session值
- 如果有从session有删除,并返回
在来说说SaveTempData的方法。判断参数,获得session,然后判断是否是不为空。然后保存到session中。
说完了TempData的Load和Save,开始说重头戏ActionInvoker.InvokeAction
public IActionInvoker ActionInvoker
{
get
{
if (_actionInvoker == null)
{
_actionInvoker = CreateActionInvoker();
}
return _actionInvoker;
}
set { _actionInvoker = value; }
}
protected virtual IActionInvoker CreateActionInvoker()
{
// Controller supports asynchronous operations by default.
return Resolver.GetService<IAsyncActionInvoker>() ?? Resolver.GetService<IActionInvoker>() ?? new AsyncControllerActionInvoker();
}
AsyncControllerActionInvoker一看就是异步执行ActionInvoker。Invoker是在ControllerActionInvoker里面实现的。
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
Contract.Assert(controllerContext.RouteData != null);
if (String.IsNullOrEmpty(actionName) && !controllerContext.RouteData.HasDirectRouteMatch())
{
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
{
AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor);
if (authenticationContext.Result != null)
{
// An authentication filter signaled that we should short-circuit the request. Let all
// authentication filters contribute to an action result (to combine authentication
// challenges). Then, run this action result.
AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
authenticationContext.Result);
InvokeActionResult(controllerContext, challengeContext.Result ?? authenticationContext.Result);
}
else
{
AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
if (authorizationContext.Result != null)
{
// An authorization filter signaled that we should short-circuit the request. Let all
// authentication filters contribute to an action result (to combine authentication
// challenges). Then, run this action result.
AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
authorizationContext.Result);
InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result);
}
else
{
if (controllerContext.Controller.ValidateRequest)
{
ValidateRequest(controllerContext);
}
IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
// The action succeeded. Let all authentication filters contribute to an action result (to
// combine authentication challenges; some authentication filters need to do negotiation
// even on a successful result). Then, run this action result.
AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
postActionContext.Result);
InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters,
challengeContext.Result ?? 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;
}
代码量有点多。开始入口检查,然后获取了controllerDescriptor和actionDescriptor(通过FindeAction方法),如果找不到actionDesriptor的话,会放回false,注释也写的很清楚。通知控制器没有匹配的方法。
然后在actionDescriptor找到的情况下,通过GetFilters的方法获得filterInfo。然后就是try Catch。对于第一个ThreadAbortException的错误忽略。第二个就是InvokeExceptionFilters错误,就是过滤器里面出错了。如果异常没有被异常筛选器处理的话(ExceptionHandled),就继续抛出。处理了那就直接调用InvokeActiuonResult.
try里面的代码我们下回分解
MVC 源码系列之控制器执行(一)的更多相关文章
- MVC 源码系列之控制器执行(二)
## 控制器的执行 上一节说道Controller中的ActionInvoker.InvokeAction public virtual bool InvokeAction(ControllerCon ...
- MVC 源码系列之控制器激活(一)
Controller的激活 上篇说到Route的使用,GetRoute的方法里面获得RouteData.然后通过一些判断,将最后的RouteData中的RouteHandler添加到context.R ...
- MVC 源码系列之控制器激活(二)之GetControllerType和GetcontrollerInstance
GetControllerType和GetcontrollerInstance GetControllerType protected internal virtual Type GetControl ...
- MVC 源码系列之路由(一)
路由系统 注释:这部分的源码是通过Refector查看UrlRoutingModule的源码编写,这部分的代码没有写到MVC中,却是MVC的入口. 简单的说一下激活路由之前的一些操作.一开始是由MVC ...
- MVC 源码系列之路由(二)
MVCParseData和Match方法的实现 ### ParseData 那么首先要了解一下ParseData. //namespace Route public string Url { get ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)
前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...
- MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)
前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...
随机推荐
- socket客户端的备份机制
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); //设定服务器的地址信息 SOCKADDR_IN addrSrv; addrSrv.sin_a ...
- Spring Boot整合Mybatis出现错误java.lang.IllegalStateException: Cannot load driver class:com.mysql.cj.jdbc.Driver
错误描述: Caused by: java.lang.IllegalStateException: Cannot load driver class: com.mysql.cj.jdbc.Driver ...
- PHP环境安全性能检查
PHP环境安全性能检查 PHP在Linux环境下安全配置是一个复杂的过程,其中涉及到很多的细节设置,在这里发出来一个脚本,通过这个脚本来检测你的PHP环境是否存在安全隐患,从而针对这些对你的PHP环境 ...
- ==和is的区别 以及编码和解码
is和==的区别 1. id() id是python的一个内置函数,通过id()可以查看变量表的值在内存中的地址. s1 = 2 print(id(s1)) # 1514368064 s2 = 2 p ...
- Linux相关TCP参数优化: proc/sys/net/ipv4/ 提高web质量
tcp_wmem(3个INTEGER变量): min, default, max min:为TCP socket预留用于发送缓冲的内存最小值.每个tcp socket都可以在建议以后都可以使用它.默认 ...
- jquery获取元素
let $lis = $('#sidebar-menu li[to]')//获取sidebar-menu下包含to属性的li
- adb打开系统设置的命令
adb命令打开手机设置页面 设置主页面adb shell am start com.android.settings/com.android.settings.Settings 安全adb shell ...
- mysql BETWEEN操作符 语法
mysql BETWEEN操作符 语法 作用:选取介于两个值之间的数据范围.这些值可以是数值.文本或者日期.大理石平台 语法:SELECT column_name(s) FROM table_name ...
- Leetcode 6. ZigZag Conversion(找规律,水题)
6. ZigZag Conversion Medium The string "PAYPALISHIRING" is written in a zigzag pattern on ...
- python 获取list某个元素下标
index() 函数用于从列表中找出某个值第一个匹配项的索引位置. list.index(x, start, end) #start end 指示搜索的起始和结尾位置,缺省为整个数组 x-- 查找的对 ...