zoukankan      html  css  js  c++  java
  • 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的逻辑说玩了,简单的说就做了三件事:

    1. 判断请求是否第一次进来
    2. 初始化ControllerContext
    3. 交给子类的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?

    1. 加载TempData
    2. 获取Action的名字
    3. 执行Action
    4. 保存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。

    1. 正常的取值的话会将数据的从initialKeys移除,如果没有调用Keep和peek的话 就会从session中去掉(也就是被使用了)
    2. 调用keep可以将单个或者所有添加的TempData保存到下一次的Session中
    3. 调用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里面的代码我们下回分解

  • 相关阅读:
    Java Web idea Filter配置(过滤器配置)
    JAVA中一些定时器的使用
    数据库事务的四大特性
    SQL语句中----删除表数据drop、truncate和delete的用法
    什么是索引?Mysql目前主要的几种索引类型
    MySQL练习题
    测试6--模拟两人在对话1000次
    实验室每日一题WP-12月7日
    实验室每日一题WP-12月5日
    实验室每日一题WP-12月4日
  • 原文地址:https://www.cnblogs.com/shaoqi/p/7384469.html
Copyright © 2011-2022 走看看