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 ITempDataProviderCreateTempDataProvider() {
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