asp.net mvc 之旅 —— 第五站 從原始碼中分析asp.net mvc 中的TempData

一線碼農發表於2016-07-12

  在mvc的controller中,我們知道有很多的臨時變數存放資料,比如說viewData,viewBag,還有一個比較特殊的tempData,關於前兩個或許大家都明白,

基本上是一個東西,就是各自的程式設計寫法不一樣,最終都會放到viewContext中,然後送到WebPage中,如果你要證明的話,可以看下下面的程式碼。

        /// <summary>Gets the dynamic view data dictionary.</summary>
        /// <returns>The dynamic view data dictionary.</returns>
        [Dynamic]
        public dynamic ViewBag
        {
            [return: Dynamic]
            get
            {
                if (this._dynamicViewDataDictionary == null)
                {
                    this._dynamicViewDataDictionary = new DynamicViewDataDictionary(() => this.ViewData);
                }
                return this._dynamicViewDataDictionary;
            }
        }

        /// <summary>Gets or sets the dictionary for view data.</summary>
        /// <returns>The dictionary for the view data.</returns>
        public ViewDataDictionary ViewData
        {
            get
            {
                if (this._viewDataDictionary == null)
                {
                    this._viewDataDictionary = new ViewDataDictionary();
                }
                return this._viewDataDictionary;
            }
            set
            {
                this._viewDataDictionary = value;
            }
        }

從上面的程式碼中可以看到,其實ViewBag就是獲取ViewData的資料,對不對。。。

 

一:TempData

    至於這個東西怎麼用,大家貌似都記得是可訪問一次後即刻消失,好像貌似也就這樣了,當然不知道有沒有人對tempdata的底層程式碼進行研究呢???

看一下它的底層到底是怎麼來實現的。

 

1. TempData原始碼

    首先我們看一下TempData的型別是TempDataDictionary,可以看到這個型別肯定是實現了IDictionary介面的自定義字典,

        public TempDataDictionary TempData
        {
            get
            {
                if (this.ControllerContext != null && this.ControllerContext.IsChildAction)
                {
                    return this.ControllerContext.ParentActionViewContext.TempData;
                }
                if (this._tempDataDictionary == null)
                {
                    this._tempDataDictionary = new TempDataDictionary();
                }
                return this._tempDataDictionary;
            }
            set
            {
                this._tempDataDictionary = value;
            }
        }

從上面程式碼可以看到,tempdate預設是new了一個TempDataDictionary類,這個類中很好玩的地方在於這裡有一個load方法,這個load方法就是獲取真

正的provider,比如下面這樣:

        /// <summary>Loads the specified controller context by using the specified data provider.</summary>
        /// <param name="controllerContext">The controller context.</param>
        /// <param name="tempDataProvider">The temporary data provider.</param>
        public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
        {
            IDictionary<string, object> dictionary = tempDataProvider.LoadTempData(controllerContext);
            this._data = ((dictionary != null) ? new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase));
            this._initialKeys = new HashSet<string>(this._data.Keys, StringComparer.OrdinalIgnoreCase);
            this._retainedKeys.Clear();
        }

這個load方法就是非常重要的,這裡的引數ITempDataProvider就是我們在BeginExecute方法賦值的,繼續往下看,不要著急哦。。。

 

2. BeginExecute

   我們知道,mvc框架其實是截獲了mvcroutehandler來進行截獲url的請求,繼而將後續的處理就由mvc框架來接管,最終會執行到Controller類下面的

BeginExecute,如果你不信,我可以開心加愉快的給你上程式碼,比如下面這樣:

        protected virtual IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state)
        {
            Action action2 = null;
            if (this.DisableAsyncSupport)
            {
                if (action2 == null)
                {
                    action2 = delegate {
                        this.Execute(requestContext);
                    };
                }
                Action action = action2;
                return AsyncResultWrapper.BeginSynchronous(callback, state, action, _executeTag);
            }
            if (requestContext == null)
            {
                throw new ArgumentNullException("requestContext");
            }
            base.VerifyExecuteCalledOnce();
            this.Initialize(requestContext);
            BeginInvokeDelegate<Controller> beginDelegate = (asyncCallback, callbackState, controller) => controller.BeginExecuteCore(asyncCallback, callbackState);
            EndInvokeVoidDelegate<Controller> endDelegate = delegate (IAsyncResult asyncResult, Controller controller) {
                controller.EndExecuteCore(asyncResult);
            };
            return AsyncResultWrapper.Begin<Controller>(callback, state, beginDelegate, endDelegate, this, _executeTag, -1, null);
        }

上面這段程式碼中,你一定要看清楚上面標紅的地方,這裡我們看到了,其實這裡是一個非同步的beginxxx,endxxx的操作,問題就是在這裡,首先我們從

beginInvoke說起。

 

<1> beginDelegate

       這個非同步操作中,我們可以看到,其實執行的是一個controller.BeginExecuteCore(asyncCallback, callbackState) 方法,對吧,然後我們可以

感興趣的看一下這個方法幹了什麼?

        protected virtual IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
        {
            IAsyncResult result;
            this.PossiblyLoadTempData();
            try
            {
                Action action2 = null;
                string actionName = GetActionName(this.RouteData);
                IActionInvoker invoker = this.ActionInvoker;
                IAsyncActionInvoker invoker = invoker as IAsyncActionInvoker;
                if (invoker != null)
                {
                    BeginInvokeDelegate<ExecuteCoreState> beginDelegate = (asyncCallback, asyncState, innerState) => innerState.AsyncInvoker.BeginInvokeAction(innerState.Controller.ControllerContext, innerState.ActionName, asyncCallback, asyncState);
                    EndInvokeVoidDelegate<ExecuteCoreState> endDelegate = delegate (IAsyncResult asyncResult, ExecuteCoreState innerState) {
                        if (!innerState.AsyncInvoker.EndInvokeAction(asyncResult))
                        {
                            innerState.Controller.HandleUnknownAction(innerState.ActionName);
                        }
                    };
                    ExecuteCoreState invokeState = new ExecuteCoreState {
                        Controller = this,
                        AsyncInvoker = invoker,
                        ActionName = actionName
                    };
                    return AsyncResultWrapper.Begin<ExecuteCoreState>(callback, state, beginDelegate, endDelegate, invokeState, _executeCoreTag, -1, null);
                }
                if (action2 == null)
                {
                    action2 = delegate {
                        if (!invoker.InvokeAction(this.ControllerContext, actionName))
                        {
                            this.HandleUnknownAction(actionName);
                        }
                    };
                }
                Action action = action2;
                result = AsyncResultWrapper.BeginSynchronous(callback, state, action, _executeCoreTag);
            }
            catch
            {
                this.PossiblySaveTempData();
                throw;
            }
            return result;
        }

從上面的程式碼中,你應該看到了有一個 this.PossiblyLoadTempData()方法,看這個名字我們大概就可以猜得到這個方法和tempdate肯定有莫大的關係。

說時遲那時快,我們可以看下這個方法到底幹了什麼。。。在一系列跟蹤之後,我們最後會到這個程式碼裡面去了,如下所示:

        internal void PossiblyLoadTempData()
        {
            if (!base.ControllerContext.IsChildAction)
            {
                base.TempData.Load(base.ControllerContext, this.TempDataProvider);
            }
        }

 

請大家看清了,這裡我們呼叫了剛才文章開頭出說到的Tempdata.Load方法,那麼問題來了,這裡的TempDataProvider到底是怎麼來的。我們繼續來看程式碼:

        public ITempDataProvider TempDataProvider
        {
            get
            {
                if (this._tempDataProvider == null)
                {
                    this._tempDataProvider = this.CreateTempDataProvider();
                }
                return this._tempDataProvider;
            }
            set
            {
                this._tempDataProvider = value;
            }
        }

 

看到沒有,然後TempDataProvider然來是呼叫了CreateTempDataProvider方法來實現的,下一步我們來看一下CreateTempDataProvider到底幹了什麼。

        protected virtual ITempDataProvider CreateTempDataProvider()
        {
            ITempDataProviderFactory service = this.Resolver.GetService<ITempDataProviderFactory>();
            if (service != null)
            {
                return service.CreateInstance();
            }
            return (this.Resolver.GetService<ITempDataProvider>() ?? new SessionStateTempDataProvider());
        }

從上面這個程式碼,我們應該就明白了,然來我們的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> dictionary = session["__ControllerTempData"] as Dictionary<string, object>;
                if (dictionary != null)
                {
                    session.Remove("__ControllerTempData");
                    return dictionary;
                }
            }
            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 flag = (values != null) && (values.Count > 0);
            if (session == null)
            {
                if (flag)
                {
                    throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
                }
            }
            else if (flag)
            {
                session["__ControllerTempData"] = values;
            }
            else if (session["__ControllerTempData"] != null)
            {
                session.Remove("__ControllerTempData");
            }
        }
    }

可以看到,SessionStateTempDataProvider 是實現了ITempDataProvider介面,裡面有兩個方法LoadTempData 和SaveTempData方法,而

LoadTempData方法的邏輯很奇葩,你可以仔細觀察一下哦,如果 if (session != null)滿足就清空字典的資料,否則就不清除,這個邏輯大概就向

你展示了為什麼資料只能被讀取一次,下次讀取的時候,就走了這個if(session!=null)給清空了,你怎麼可能再讀取session中的資料呢。。。這個

就是為什麼tempdata只能被讀取一次的真相,是不是很好玩。

 

<2> EndExecuteCore

    有人可能會問了,第二個方法SaveTempData是什麼時候執行的,當然就是EndExecuteCore裡面了,比如你看:

        protected virtual void EndExecuteCore(IAsyncResult asyncResult)
        {
            try
            {
                AsyncResultWrapper.End(asyncResult, _executeCoreTag);
            }
            finally
            {
                this.PossiblySaveTempData();
            }
        }

可以看到它的預設實現是session,當然你也可以實現一個自定義的provider,比如用cache來存放這個臨時資料,或者是redis,mongodb等等。。。

當然還有更多有趣的東西等待你發掘哦~~~

 

相關文章