控制器的执行

之前说了Controller的激活,现在看一个激活Controller之后控制器内部的主要实现。

  1. public interface IController
  2. {
  3. void Execute(RequestContext requestContext);
  4. }

先看一下IController的接口,就一个方法Execute的方法,参数为RequestConext。

  1. graph TD
  2. IController-->ControllerBase
  3. ControllerBase-->Controller

上图是Controller的简单的继承关系。

IContrller接口的Execute被ControllerBase实现了。具体实现的代码如下。

  1. void IController.Execute(RequestContext requestContext)
  2. {
  3. Execute(requestContext);
  4. }
  5. protected virtual void Execute(RequestContext requestContext)
  6. {
  7. if (requestContext == null)
  8. {
  9. throw new ArgumentNullException("requestContext");
  10. }
  11. if (requestContext.HttpContext == null)
  12. {
  13. throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
  14. }
  15. VerifyExecuteCalledOnce();
  16. Initialize(requestContext);
  17. using (ScopeStorage.CreateTransientScope())
  18. {
  19. ExecuteCore();
  20. }
  21. }

首先显示的实现了IController的接口,然后调用本身的Execute的方法。VerifyExecuteCalledOnce()看方法名的意思应该是验证是否是第一次执行。这个后面细说。然后调用了Initialize().

  1. protected virtual void Initialize(RequestContext requestContext)
  2. {
  3. ControllerContext = new ControllerContext(requestContext, this);
  4. }

很简单就是将请求的上下文和Controller做个了绑定。但是方法是一个虚方法,看看在子类里面有没有重写?

  1. protected override void Initialize(RequestContext requestContext)
  2. {
  3. base.Initialize(requestContext);
  4. Url = new UrlHelper(requestContext);
  5. }

重写了,对UrlHelper进行了一个初始化,应该是一个Url的工具类。然后useing了一个作用域?也不知道拿来干嘛,就调用了子类(Controller)里面的ExecuteCore().整个逻辑应该差不多了。回头来看看VerifyExecuteCalledOnce的具体实现。

  1. private readonly SingleEntryGate _executeWasCalledGate = new SingleEntryGate();
  2. internal void VerifyExecuteCalledOnce()
  3. {
  4. if (!_executeWasCalledGate.TryEnter())
  5. {
  6. string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType());
  7. throw new InvalidOperationException(message);
  8. }
  9. }

调用了_executeWasCalledGate属性的TryEnter()方法,如果放回为false就报错。而executeWasCalledGate是SingleEntryGate的实例。看看SingleEntryGate的结构吧。

  1. internal sealed class SingleEntryGate
  2. {
  3. private const int NotEntered = 0;
  4. private const int Entered = 1;
  5. private int _status;
  6. // returns true if this is the first call to TryEnter(), false otherwise
  7. public bool TryEnter()
  8. {
  9. int oldStatus = Interlocked.Exchange(ref _status, Entered);
  10. return (oldStatus == NotEntered);
  11. }
  12. }

简单说一下,其实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的逻辑说玩了,简单的说就做了三件事:

  1. 判断请求是否第一次进来
  2. 初始化ControllerContext
  3. 交给子类的ExecuteCore方法中去

现在看一下Controller里面的实现:

  1. protected override void ExecuteCore()
  2. {
  3. // If code in this method needs to be updated, please also check the BeginExecuteCore() and
  4. // EndExecuteCore() methods of AsyncController to see if that code also must be updated.
  5. PossiblyLoadTempData();
  6. try
  7. {
  8. string actionName = GetActionName(RouteData);
  9. if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
  10. {
  11. HandleUnknownAction(actionName);
  12. }
  13. }
  14. finally
  15. {
  16. PossiblySaveTempData();
  17. }
  18. }

PossiblyLoadTempData 看方法名应该是加载TempData.然后获取到actionName,调用ActionInvoker.InvokeAction如果返回false的话,就抛出错误。然后保存TempData?

  1. 加载TempData
  2. 获取Action的名字
  3. 执行Action
  4. 保存TempataData

还是把 PossiblyLoadTempData和PossiblySaveTempData 单独拿出来看看里面的实现细节:

  1. internal void PossiblyLoadTempData()
  2. {
  3. if (!ControllerContext.IsChildAction)
  4. {
  5. TempData.Load(ControllerContext, TempDataProvider);
  6. }
  7. }
  8. internal void PossiblySaveTempData()
  9. {
  10. if (!ControllerContext.IsChildAction)
  11. {
  12. TempData.Save(ControllerContext, TempDataProvider);
  13. }
  14. }

可以看到,首先判断是否不是子的Action,然后调用了TempData的Load和Save.重点看的是TempData和TempDateProvider这两个。

一个是方法的拥有者,一个是方法的参数。

  1. public TempDataDictionary TempData
  2. {
  3. get
  4. {
  5. if (ControllerContext != null && ControllerContext.IsChildAction)
  6. {
  7. return ControllerContext.ParentActionViewContext.TempData;
  8. }
  9. if (_tempDataDictionary == null)
  10. {
  11. _tempDataDictionary = new TempDataDictionary();
  12. }
  13. return _tempDataDictionary;
  14. }
  15. set { _tempDataDictionary = value; }
  16. }

F12,就可以看到代码的源码了。 ControllerContext.IsChildAction在这个执行环境中为false,所以不会到第一个if里面去。所以是第二个if,TempData的类型也就出来了是TempDataDictionary。这边也可以看出如果是ChildAction会调用父级的TempData。

  1. public class TempDataDictionary : IDictionary<string, object>
  2. {
  3. private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  4. private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  5. public object this[string key]
  6. {
  7. get
  8. {
  9. object value;
  10. if (TryGetValue(key, out value))
  11. {
  12. _initialKeys.Remove(key);
  13. return value;
  14. }
  15. return null;
  16. }
  17. set
  18. {
  19. _data[key] = value;
  20. _initialKeys.Add(key);
  21. }
  22. }
  23. public void Keep()
  24. {
  25. _retainedKeys.Clear();
  26. _retainedKeys.UnionWith(_data.Keys);
  27. }
  28. public void Keep(string key)
  29. {
  30. _retainedKeys.Add(key);
  31. }
  32. public object Peek(string key)
  33. {
  34. object value;
  35. _data.TryGetValue(key, out value);
  36. return value;
  37. }
  38. public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
  39. {
  40. IDictionary<string, object> providerDictionary = tempDataProvider.LoadTempData(controllerContext);
  41. _data = (providerDictionary != null)
  42. ? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase)
  43. : new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
  44. _initialKeys = new HashSet<string>(_data.Keys, StringComparer.OrdinalIgnoreCase);
  45. _retainedKeys.Clear();
  46. }
  47. public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
  48. {
  49. // Frequently called so ensure delegate is stateless
  50. _data.RemoveFromDictionary((KeyValuePair<string, object> entry, TempDataDictionary tempData) =>
  51. {
  52. string key = entry.Key;
  53. return !tempData._initialKeys.Contains(key)
  54. && !tempData._retainedKeys.Contains(key);
  55. }, this);
  56. tempDataProvider.SaveTempData(controllerContext, _data);
  57. }
  58. }

TempDataDictionary本身继承了字典类型。首先Load,代码的第一句就获取了tempDataProvider.LoadTempData获得providerDictionary,然后将获得的数组都忽略大小写。然后给_initialKeys赋值。对_retainedKeys进行clear()。_initialKeys和_retainedKeys这两个都是一个HashSet的集合。这两个集合可以决定在Save的方法的时候数据要不要保存到session里面。看到save里面如果key不存在_initialKeys和_retainedKeys两个数组里面的话便删除。对着两个数组进行操作的方法有三个:集合正常的取值,Keep和Peek。

  1. 正常的取值的话会将数据的从initialKeys移除,如果没有调用Keep和peek的话 就会从session中去掉(也就是被使用了)
  2. 调用keep可以将单个或者所有添加的TempData保存到下一次的Session中
  3. 调用peek可以取值,然后将值保存到下一个请求中去。

    然后来看看这个参数。
  1. public ITempDataProvider TempDataProvider
  2. {
  3. get
  4. {
  5. if (_tempDataProvider == null)
  6. {
  7. _tempDataProvider = CreateTempDataProvider();
  8. }
  9. return _tempDataProvider;
  10. }
  11. set { _tempDataProvider = value; }
  12. }
  13. protected virtual ITempDataProvider CreateTempDataProvider()
  14. {
  15. return Resolver.GetService<ITempDataProvider>() ?? new SessionStateTempDataProvider();
  16. }

看到Resolver我们应该很熟悉了,可以通过自定义或者第三方的类去注册到MVC里面,对TempData进行加载和保存。基本会走默认的类型。所以实际上调用的类型是SessionStateTempDataProvider。

来看看SessionStateTempDataProvider的方法。

  1. public class SessionStateTempDataProvider : ITempDataProvider
  2. {
  3. internal const string TempDataSessionStateKey = "__ControllerTempData";
  4. public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
  5. {
  6. HttpSessionStateBase session = controllerContext.HttpContext.Session;
  7. if (session != null)
  8. {
  9. Dictionary<string, object> tempDataDictionary = session[TempDataSessionStateKey] as Dictionary<string, object>;
  10. if (tempDataDictionary != null)
  11. {
  12. // If we got it from Session, remove it so that no other request gets it
  13. session.Remove(TempDataSessionStateKey);
  14. return tempDataDictionary;
  15. }
  16. }
  17. return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
  18. }
  19. public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
  20. {
  21. if (controllerContext == null)
  22. {
  23. throw new ArgumentNullException("controllerContext");
  24. }
  25. HttpSessionStateBase session = controllerContext.HttpContext.Session;
  26. bool isDirty = (values != null && values.Count > 0);
  27. if (session == null)
  28. {
  29. if (isDirty)
  30. {
  31. throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
  32. }
  33. }
  34. else
  35. {
  36. if (isDirty)
  37. {
  38. session[TempDataSessionStateKey] = values;
  39. }
  40. else
  41. {
  42. // Since the default implementation of Remove() (from SessionStateItemCollection) dirties the
  43. // collection, we shouldn't call it unless we really do need to remove the existing key.
  44. if (session[TempDataSessionStateKey] != null)
  45. {
  46. session.Remove(TempDataSessionStateKey);
  47. }
  48. }
  49. }
  50. }
  51. }

主要就两个方法LoadTempData和SaveTempData也分别在前面的方法中被调用了。首先看LoadTempData。获得请求的Session,看其中有没有__ControllerTempData为key的session。如果有的话返回,并将它在session中删除。注释是这样解释的,防止其他的请求获得这个session。简单的逻辑。

  • 获得session
  • 配备__ControllerTempData的session值
  • 如果有从session有删除,并返回

在来说说SaveTempData的方法。判断参数,获得session,然后判断是否是不为空。然后保存到session中。


说完了TempData的Load和Save,开始说重头戏ActionInvoker.InvokeAction

  1. public IActionInvoker ActionInvoker
  2. {
  3. get
  4. {
  5. if (_actionInvoker == null)
  6. {
  7. _actionInvoker = CreateActionInvoker();
  8. }
  9. return _actionInvoker;
  10. }
  11. set { _actionInvoker = value; }
  12. }
  13. protected virtual IActionInvoker CreateActionInvoker()
  14. {
  15. // Controller supports asynchronous operations by default.
  16. return Resolver.GetService<IAsyncActionInvoker>() ?? Resolver.GetService<IActionInvoker>() ?? new AsyncControllerActionInvoker();
  17. }

AsyncControllerActionInvoker一看就是异步执行ActionInvoker。Invoker是在ControllerActionInvoker里面实现的。

  1. public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
  2. {
  3. if (controllerContext == null)
  4. {
  5. throw new ArgumentNullException("controllerContext");
  6. }
  7. Contract.Assert(controllerContext.RouteData != null);
  8. if (String.IsNullOrEmpty(actionName) && !controllerContext.RouteData.HasDirectRouteMatch())
  9. {
  10. throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
  11. }
  12. ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
  13. ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
  14. if (actionDescriptor != null)
  15. {
  16. FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
  17. try
  18. {
  19. AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor);
  20. if (authenticationContext.Result != null)
  21. {
  22. // An authentication filter signaled that we should short-circuit the request. Let all
  23. // authentication filters contribute to an action result (to combine authentication
  24. // challenges). Then, run this action result.
  25. AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
  26. controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
  27. authenticationContext.Result);
  28. InvokeActionResult(controllerContext, challengeContext.Result ?? authenticationContext.Result);
  29. }
  30. else
  31. {
  32. AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
  33. if (authorizationContext.Result != null)
  34. {
  35. // An authorization filter signaled that we should short-circuit the request. Let all
  36. // authentication filters contribute to an action result (to combine authentication
  37. // challenges). Then, run this action result.
  38. AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
  39. controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
  40. authorizationContext.Result);
  41. InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result);
  42. }
  43. else
  44. {
  45. if (controllerContext.Controller.ValidateRequest)
  46. {
  47. ValidateRequest(controllerContext);
  48. }
  49. IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
  50. ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
  51. // The action succeeded. Let all authentication filters contribute to an action result (to
  52. // combine authentication challenges; some authentication filters need to do negotiation
  53. // even on a successful result). Then, run this action result.
  54. AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
  55. controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
  56. postActionContext.Result);
  57. InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters,
  58. challengeContext.Result ?? postActionContext.Result);
  59. }
  60. }
  61. }
  62. catch (ThreadAbortException)
  63. {
  64. // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
  65. // the filters don't see this as an error.
  66. throw;
  67. }
  68. catch (Exception ex)
  69. {
  70. // something blew up, so execute the exception filters
  71. ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
  72. if (!exceptionContext.ExceptionHandled)
  73. {
  74. throw;
  75. }
  76. InvokeActionResult(controllerContext, exceptionContext.Result);
  77. }
  78. return true;
  79. }
  80. // notify controller that no method matched
  81. return false;
  82. }

代码量有点多。开始入口检查,然后获取了controllerDescriptor和actionDescriptor(通过FindeAction方法),如果找不到actionDesriptor的话,会放回false,注释也写的很清楚。通知控制器没有匹配的方法。

然后在actionDescriptor找到的情况下,通过GetFilters的方法获得filterInfo。然后就是try Catch。对于第一个ThreadAbortException的错误忽略。第二个就是InvokeExceptionFilters错误,就是过滤器里面出错了。如果异常没有被异常筛选器处理的话(ExceptionHandled),就继续抛出。处理了那就直接调用InvokeActiuonResult.

try里面的代码我们下回分解

MVC 源码系列之控制器执行(一)的更多相关文章

  1. MVC 源码系列之控制器执行(二)

    ## 控制器的执行 上一节说道Controller中的ActionInvoker.InvokeAction public virtual bool InvokeAction(ControllerCon ...

  2. MVC 源码系列之控制器激活(一)

    Controller的激活 上篇说到Route的使用,GetRoute的方法里面获得RouteData.然后通过一些判断,将最后的RouteData中的RouteHandler添加到context.R ...

  3. MVC 源码系列之控制器激活(二)之GetControllerType和GetcontrollerInstance

    GetControllerType和GetcontrollerInstance GetControllerType protected internal virtual Type GetControl ...

  4. MVC 源码系列之路由(一)

    路由系统 注释:这部分的源码是通过Refector查看UrlRoutingModule的源码编写,这部分的代码没有写到MVC中,却是MVC的入口. 简单的说一下激活路由之前的一些操作.一开始是由MVC ...

  5. MVC 源码系列之路由(二)

    MVCParseData和Match方法的实现 ### ParseData 那么首先要了解一下ParseData. //namespace Route public string Url { get ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  7. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

随机推荐

  1. js中封装一个自己的简单数学对象

    封装一个数学对象求最大值最小值 <script> var myMath={ PI:3.1415926, max:function(){ var max=arguments[0];//注意a ...

  2. XPath语法以及谓语的结合使用

    /* XPath 术语 节点(Node) 在 XPath 中,有七种类型的节点:元素.属性.文本.命名空间.处理指令.注释以及文档(根)节点.XML 文档是被作为节点树来对待的.树的根被称为文档节点或 ...

  3. Apache 安装后启动出现的错误

    错误信息 1: configure: error: APR not found 解决方法:yum install apr* -y 错误信息 2:httpd: apr_sockaddr_info_get ...

  4. odoo ERP 系统安装与使用

    https://hub.docker.com/_/odoo/ #!/bin/bash sudo docker pull postgres:10sudo docker pull odoo:11.0 su ...

  5. Mongo--02 命令介绍

    目录 Mongo工具 1. 查看指令 2.插入命令 3.查询命令 4.更新数据 5.索引 5.删除 6.mongo命令介绍 7.创建用户和角色 Mongo工具 1. 查看指令 test:登录时默认存在 ...

  6. 2019 计蒜之道 复赛 B. 个性化评测系统 (模拟,实现,暴搜)

    24.02% 1000ms 262144K "因材施教"的教育方式自古有之,互联网时代,要实现真正意义上的个性化教育,离不开大数据技术的扶持.VIPKID 英语 2020 多万学员 ...

  7. 【串线篇】spring boot整合SpringData JPA

    一.SpringData简介 其中SpringData JPA底层基于hibernate 二.整合SpringData JPA JPA: Java Persistence API的简称,中文名Java ...

  8. 注册和登录(关于Cookie)

    前记 我将描述一下登陆和注册之间发生了什么,将场景分为客户端和服务端,服务器是Node.JS,客户端是由JS写的 注册 1.注册请求 这是由客户端发送一个POST请求给服务端,其中包含了用户名和密码 ...

  9. java -cp与java -jar的区别

    java -cp 和 -classpath 一样,是指定类运行所依赖其他类的路径,通常是类库,jar包之类,需要全路径到jar包,window上分号“;”格式:java -cp .;myClass.j ...

  10. MySQL数据库3分组与单表、多表查询

    目录 一.表操作的补充 1.1null 和 not null 1.2使用not null的时候 二.单表的操作(import) 2.1分组 2.1.1聚合函数 2.1.2group by 2.1.3h ...