asp.net mvc 之旅 —— 第五站 從原始碼中分析asp.net mvc 中的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等等。。。
當然還有更多有趣的東西等待你發掘哦~~~