asp.net mvc源碼分析-Controllerl篇 TempData數據存儲


本些列文章是以asp.net mvc源代碼為例按照asp.net mvc執行順序一一分析和解釋。上篇文章asp.net mvc源碼分析-Controllerl篇 如何創建Controller實例 講到了如何創建Controller,在創建后就調用  controller.Execute(RequestContext);

在ControllerBase的Execute方法很簡單

  VerifyExecuteCalledOnce(); // 確保一個controller實例只調用一次,
            Initialize(requestContext);//初始化 ControllerContext = new ControllerContext(requestContext, this);
            using (ScopeStorage.CreateTransientScope()) {
                ExecuteCore();//這個才是真正的執行
            }

本系列文章主要是分析源代碼,分析里面的邏輯和實現細節,所以我們還是來看看VerifyExecuteCalledOnce這個方法吧。

 internal void VerifyExecuteCalledOnce() {
            if (!_executeWasCalledGate.TryEnter()) {
                string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType());
                throw new InvalidOperationException(message);
            }
        }

  internal sealed class SingleEntryGate {
        private const int NOT_ENTERED = 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 == NOT_ENTERED);

        }
    }

當大家 看了TryEnter方法以后是不是覺得他們實現的很巧妙啊。保證一個類的一個實例方法只執行一次的一種實現方式。

而ExecuteCore這個方法在抽象類Controller中實現,Controller是ControllerBase的子類,

  protected override void ExecuteCore() {
            PossiblyLoadTempData();
            try {
                string actionName = RouteData.GetRequiredString("action");
                if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
                    HandleUnknownAction(actionName);
                }
            }
            finally {
                PossiblySaveTempData();
            }
        } 

其中 ActionInvoker.InvokeAction(ControllerContext, actionName)是真正的調用Action,我們放到后面來講,這節我們來看看PossiblyLoadTempData、PossiblySaveTempData這個2個方法。在每次action調用前加載,調用后保存。

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

這 2個方法實現是不特別簡單啊,那么TempData屬性實現是否簡單了?

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

這里 需要注意一下的是如果當前Action是一個子Action則返回父輩Action的Controller的TempData。

一提到 TempData ,我們還知道ViewData、ViewBag也是保存數據的,它們之間有何區別了?

TempData 是TempDataDictionary類的實例  public class TempDataDictionary : IDictionary<string, object> 

ViewData是ViewDataDictionary類的實例 public class ViewDataDictionary : IDictionary<string, object> 

ViewBag是DynamicViewDataDictionary類的實例 internal sealed class DynamicViewDataDictionary : DynamicObject

一般 對它們的區別網上都是如下的內容:

TempData:保存在Session中,Controller每次執行請求的時候,會從Session中先獲取TempData,而后清除Session,獲取完TempData數據,雖然保存在內部字典對象中,但是其集合中的每個條目訪問一次后就從字典表中刪除。具體代碼層面,TempData獲取過程是通過SessionStateTempDataProvider.LoadTempData方法從ControllerContext的Session中讀取數據,而后清除Session,故TempData只能跨Controller傳遞一次。
ViewData:生命周期和View相同,僅對當前View有效。
ViewBag:和ViewData生命周期相同,也是對但前View有效,不同的是ViewBag的類型不再是字典的鍵值對結構,而是dynamic動態類型,屬於MVC3里面新增的部分。
這里的TempData解釋是對的嗎?
我們 這里主要講講TempData,其他2個很簡單,TempDataDictionary類主要代碼如下:
 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) {
            string[] keysToKeep = _initialKeys.Union(_retainedKeys, StringComparer.OrdinalIgnoreCase).ToArray();
            string[] keysToRemove = _data.Keys.Except(keysToKeep, StringComparer.OrdinalIgnoreCase).ToArray();
            foreach (string key in keysToRemove) {
                _data.Remove(key);
            }
            tempDataProvider.SaveTempData(controllerContext, _data);
        }

        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 Add(string key, object value) {
            _data.Add(key, value);
            _initialKeys.Add(key);
        }
仔細看以上代碼,我們會發現Load只是初始化一個默認的字典,沒什么特別的, 而Save就有所特別,它在每次保存的時候都移除此次添加的key,說白了又回到初始狀態了。反正我是沒明白微軟為什么要這個做。不保存不就行了嗎?
在來讓我們看看TempDataProvider 
        public ITempDataProvider TempDataProvider {
            get {
                if (_tempDataProvider == null) {
                    _tempDataProvider = CreateTempDataProvider();
                }
                return _tempDataProvider;
            }
             set {
                _tempDataProvider = value;
            }

        }
   protected  virtual ITempDataProvider CreateTempDataProvider() {
            return new SessionStateTempDataProvider();
        }
微軟代碼就這樣,看看這 如果我們要設置自己的TempDataProvider 可以通過TempDataProvider 屬性來設置,也可以通過重寫CreateTempDataProvider方法來實現,總是提供給用戶多個選擇。
我們還是來看看默認的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方法吧, 第一次訪問tempDataDictionary應該是空的沒有任何數據,直接new一個字典。然后就是SaveTempData了,按照前面的理解,這個時候的字典應該沒有數據了,一旦有它就是臟數據。
 bool isDirty = (values != null && values.Count > 0);
所以 真正的保存執行代碼是
if (session[TempDataSessionStateKey] != null) {
                        session.Remove(TempDataSessionStateKey);
                    }
移除 session,以至於第二次又重新實例一個字典。但是有一種情況很特殊也是經常遇到的,也是TempData存在的原因。
我們以一段代碼來說明吧:
為什么了會這樣了,原因很簡單,雖然我們調用@{Html.RenderAction("Index","Test");}時候會去執行  PossiblyLoadTempData()、 PossiblySaveTempData()這2個方法,但是他們有一個過濾條件  if (!ControllerContext.IsChildAction) {} 這個條件不滿足,所以實際上就沒有調用TempData.Load和TempData.Save方法。IsChildAction這個屬性究竟是怎么定義的了。
   public virtual bool IsChildAction {
            get {
                RouteData routeData = RouteData;
                if (routeData == null) {
                    return false;
                }
                return routeData.DataTokens.ContainsKey(PARENT_ACTION_VIEWCONTEXT);
            }
        }
而RenderAction實際上市調用
  internal static void ActionHelper(HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues, TextWriter textWriter) 
各參數如下:
actionName:Index
controllerName:Test
routeValues:null
textWriter:htmlHelper.ViewContext.Writer
在這個方法里面有一句   RouteData routeData = CreateRouteData(vpd.Route, routeValues, vpd.DataTokens, htmlHelper.ViewContext);
該方法的代碼如下:
    private static RouteData CreateRouteData(RouteBase route, RouteValueDictionary routeValues, RouteValueDictionary dataTokens, ViewContext parentViewContext) {
            RouteData routeData = new RouteData();
            foreach (KeyValuePair<string, object> kvp in routeValues) {
                routeData.Values.Add(kvp.Key, kvp.Value);
            }
            foreach (KeyValuePair<string, object> kvp in dataTokens) {
                routeData.DataTokens.Add(kvp.Key, kvp.Value);
            }
            routeData.Route = route;
             routeData.DataTokens[ControllerContext.PARENT_ACTION_VIEWCONTEXT] = parentViewContext;
            return routeData;
        }
我想看到這里大家都應該明白了吧 TempData也可次訪問。應該是說MVC在請求周期結束的時候有動作去刪除此類的Session,而不是訪問一次就被刪除。MS命名為TempData,意思應該是說TempData是個Session,但是它又和普通的Session不同。它會在請求之后被刪除,所以是臨時的Data
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM