asp.Netmvcの旅-第5局はソースコードからaspを分析する.NetmvcでのTempData

26042 ワード

mvcのcontrollerでは、viewData、viewBag、さらに特殊なtempDataなど、多くの一時変数がデータを格納していることを知っています.
基本的には、それぞれのプログラミングの書き方が異なり、最終的にはviewContextに入れてWebPageに送り、証明するなら次のコードを見ることができます.
        /// Gets the dynamic view data dictionary.
        /// The dynamic view data dictionary.
        [Dynamic]
        public dynamic ViewBag
        {
            [return: Dynamic]
            get
            {
                if (this._dynamicViewDataDictionary == null)
                {
                    this._dynamicViewDataDictionary = new DynamicViewDataDictionary(() => this.ViewData);
                }
                return this._dynamicViewDataDictionary;
            }
        }

        /// Gets or sets the dictionary for view data.
        /// The dictionary for the view data.
        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は、次のようになります.
        /// Loads the specified controller context by using the specified data provider.
        /// The controller context.
        /// The temporary data provider.
        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 beginDelegate = (asyncCallback, callbackState, controller) => controller.BeginExecuteCore(asyncCallback, callbackState);
            EndInvokeVoidDelegate endDelegate = delegate (IAsyncResult asyncResult, Controller controller) {
                controller.EndExecuteCore(asyncResult);
            };
            return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, this, _executeTag, -1, null);
        }

上のこのコードの中で、あなたはきっと上の赤い地方をはっきり見て、ここで私达は见て、実はここは1つの非同期の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 beginDelegate = (asyncCallback, asyncState, innerState) => innerState.AsyncInvoker.BeginInvokeAction(innerState.Controller.ControllerContext, innerState.ActionName, asyncCallback, asyncState);
                    EndInvokeVoidDelegate 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(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();
            if (service != null)
            {
                return service.CreateInstance();
            }
            return (this.Resolver.GetService() ?? 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の2つの方法があり、
LoadTempDataメソッドの論理は奇抜で、よく観察してみてください.if(session!=null)が満足すれば辞書のデータをクリアします.そうしないとクリアしません.この論理はたぶん
なぜデータが一度しか読み取れないのかを示しましたが、次の読み込みの時に、このif(session!=null)を空にしてしまったのに、sessionのデータを再び読み取ることはできません.のこれ
なぜtempdataは一度しか読み取れないのか、面白いのではないでしょうか.
 
<2> EndExecuteCore
2つ目の方法SaveTempDataはいつ実行されたのかと聞かれるかもしれませんが、もちろんEndExecuteCoreの中にあります.例えば、見てください.
        protected virtual void EndExecuteCore(IAsyncResult asyncResult)
        {
            try
            {
                AsyncResultWrapper.End(asyncResult, _executeCoreTag);
            }
            finally
            {
                this.PossiblySaveTempData();
            }
        }

デフォルトの実装はsessionであることがわかります.もちろん、cacheでこの一時データを保存したり、redis、mongodbなどのカスタムproviderを実装したりすることもできます.の
もちろんもっと面白いものが発掘されるのを待っていますよ~~~