說明: 原文作者賢新 原文地址:http://www.cnblogs.com/chenxinblogs/p/4852813.html ViewData和ViewBag主要用於將數據從控制器中傳遞到視圖中去,ViewData本身就是一個字典。以KeyValue的形式存取值。ViewData的Value ...
說明: 原文作者賢新
原文地址:http://www.cnblogs.com/chenxinblogs/p/4852813.html
ViewData和ViewBag主要用於將數據從控制器中傳遞到視圖中去,ViewData本身就是一個字典。以KeyValue的形式存取值。ViewData的Value類型是Object,也就是可以將任意類型的值存儲到ViewData中去,平時我們都在控制器中直接使用ViewData.本質上ViewData只是Controller父類ControllerBase中的一個屬性,其類型是ViewDataDictionary,因為我們在自己的Controller中並未定義一個叫做ViewData的屬性,也就是說當我們訪問在某個類的屬性或者方法中所訪問的某個方法或者屬性中沒有找到時,我們就要想到這個屬性或者方法是否在父類中已經定義了,這個對於一個新手來說往往是容易忽略的,TempData是用於解決在不同的的Action方法之間跳轉的時候的數據傳遞。這裡不同的Action可以是同一個Controller下的不同的Action之間,也可以是不同Controller的Action之間。有些人說,利用Session不是也可以實現嗎?是的,沒錯,不過仔細的去看下微軟的Mvc源碼,你會發現,其實TempData中的數據的維護也是用到了Session的。
ViewData
我們很經常看到這樣,
public ActionResult Index()
{
//從資料庫中讀取產品列表
List<Product> productList = db.Products.ToList();
//這個時候productList這個集合對象將被傳遞到視圖頁中去.
//如果此時,在視圖頁面中使用@model List<Product> 聲名,你會發現在視圖頁面中,直接訪問View
//註意,每個視圖頁面在網站第一次被請求時,都會被編譯成一個對應的
//一個類,這些視圖類都會被編譯到一個臨時的程式集中去,這個臨時的程式集的位置,可以通過在視圖頁面中編寫代碼@this.GetType().Assembly.Locaiton
//的方式來查看其位置,實際上就是在C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\vs的某個目錄下。
//這個類直接或者間接繼承自:System.Web.Mvc.WebViewPage類.
return View(productList);
}
細心的查看View()方法的源碼:
protected internal virtual ViewResult View(string viewName, string masterName, object model)
{
if (model != null)
{
//看到了沒有,其實就是直接將我們想要傳遞到視圖頁的數據,保存到了父類ControllerBase的ViewData屬性對象的一個名為Model的屬性中去了。
//然後在將Controller中的ViewData的引用傳遞給視圖頁面類的實例對象上的ViewData屬性,這樣就能將在控制器的Action中往ViewData中設置的值傳遞到視圖中去了,也就是我們上面的代碼還可以改為
//ViewData.Model=productList;return View();就行了。
base.ViewData.Model = model;
}
return new ViewResult { ViewName = viewName, MasterName = masterName, ViewData = base.ViewData, TempData = base.TempData, ViewEngineCollection = this.ViewEngineCollection };
}
需要註意的是:ViewData中的數據只能傳遞到當前這個Action所要去載入的視圖頁面中去,而不能跨Action傳輸。
ViewBag
ViewBag,其實內部真正存儲數據的還是ViewData,也就是說,ViewData和ViewBag的數據是共用的,通過ViewData設置的數據,可以通過ViewBag訪問,通過ViewBag設置的數據可以通過
ViewData訪問。
看看ControllerBase中的ViewBag屬性的源碼:
[Dynamic]
public object ViewBag
{
[return: Dynamic]
get
{
Func<ViewDataDictionary> viewDataThunk = null;
if (this._dynamicViewDataDictionary == null)
{
if (viewDataThunk == null)
{
viewDataThunk = () => this.ViewData;
}
//viewDataThunk是個lambda表達式,返回ViewData.也就是DynamicViewDataDictionary內部還是用的是ViewData.
this._dynamicViewDataDictionary = new DynamicViewDataDictionary(viewDataThunk);
}
return this._dynamicViewDataDictionary;
}
}
以下是DynamicViewDataDictionary中的兩個方法。
public override bool TryGetMember(GetMemberBinder binder, out object result)
{ //看到重點了吧。
result = this.ViewData[binder.Name];
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
this.ViewData[binder.Name] = value;
return true;
}
TempData
我們需要知道的是,.NET Mvc中最終處理來自於瀏覽器端的請求的是一個MvcHandler的類,這個類實現了IHttpHandler,而IHttpHandler中定義了一個ProccessRequest方法,和WebForm
不一樣的是,Controller對象的創建是在MvcHandler中來完成的。從MvcHanlder的ProccessRequest方法中開始追蹤,我們會發現以下代碼:
protected internal virtual void ProcessRequest(HttpContextBase httpContext)
{
IController controller;
IControllerFactory factory;
this.ProcessRequestInit(httpContext, out controller, out factory);
try
{
controller.Execute(this.RequestContext);
}
finally
{
factory.ReleaseController(controller);
}
}
再看看controller.Execute的源碼(因為controller變數是IController介面類型,所以要查看實現了IController的類的Execute方法)發現是ControllerBase實現了該介面中的Execute方法,再看看,ControllerBase中的Execute方法,發現調用了自己的ExecuteCore方法,發現ExecuteCore是一個abstract方法,沒有方法體,然後我們從其子類Controller中看到了重寫了其父類ControllerBase中的ExecuteCore方法,Controller->ExecuteCore方法如下:
protected override void ExecuteCore()
{
//這句代碼表示在調用目標Action之前,去Session中載入對應的來自於上個Action //中保存的傳遞過來的數據。
this.PossiblyLoadTempData();
try
{
string requiredString = this.RouteData.GetRequiredString("action");
if (!this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString))
{
this.HandleUnknownAction(requiredString);
}
}
finally
{
this.PossiblySaveTempData();
}
}
我們在看看PosiblyLoadTempData這個方法中的代碼,很明顯,這個方法就是Controller中的.
internal void PossiblyLoadTempData()
{
//這裡說明瞭,只有當前被請求的不是子Action的時候才會去載入對應的TempData數據,從Session中。
if (!base.ControllerContext.IsChildAction)
{
//TempData的類型是TempDataDictionary,我們看看這個類中的Load方法,查看TempDataDictionary.Load方法,我們發現其實真正去載入的是一個實現了ITempDataProvider介面的某個類的實例對象去載入的。 //其實就是SessionStateTempDataProvider,找到這個類,查看其LoadTempData方法。
base.TempData.Load(base.ControllerContext, this.TempDataProvider);
}
}
下麵是SessionStateTempDataProvider->LoadTempData方法源碼:
// Methods
public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
{
HttpSessionStateBase session = controllerContext.HttpContext.Session;
if (session != null)
{
//真相大白了,其實我們從TempData中取數據時,還是從一個key為__ControllerTempData的Session中取出來的,也就是說TempData只是一個臨時的數據保存的地方, //最終在調用Action完畢後,框架自動把在Action中往TempData中設置的值保存到Session中去,然後跳轉到下個Action併在這個Action執行之前,又從Session中取出來, //通過as Dictionary讓我們也知道了,其實TempData中保存數據的就是一個普通的字典而已,這就是為什麼TempData能在不同的請求之間保存數據,同時也說明瞭為什麼能在多個不同的Action之間無限的進行 //數據傳遞。
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);
}
下麵是SessionStateTempDataProvider的SaveTempData方法
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");
}
}
再回頭看看Controller類中的ExecuteCore方法,其實就是在瀏覽器發出請求之後,調用控制器的某個Action之前,先會去Session中嘗試找Key為__ControllerTempData的Session["__ControllerTempData"]是否為null,如果不為null,那麼就將其取出,並且轉換為Dictionary<string,object>類型,並且存儲到ControllerBase父類中的TempData屬性對象里的內部屬性_data中去,
然後我們在Action中或者視圖中取出TempData的值的時候,就是從這個內部字典_data中取值的,在執行完action並且執行完視圖頁面的代碼之後(this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString這句代碼會去找到對應的視圖頁面類,並且去執行,),最後一個步驟,就是去將TempData中內部字典的內容保存到Session中去,因為是最後執行,所以,在視圖頁面中保存到TempData的值也會被保存起來,以供下次使用,也就是說TempData是跨請求的,但是你會發現如果經過了兩次請求,也就是從瀏覽器中輸入兩次,你會發現只能取一次,為什麼呢,看下麵這個TempDataDictionary的Save方法。
public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
{ //也就是保存到Session之前,會去先看看這些key是否是通過我們手動TempData[""]的方式設置進去的 //(之所以這種判斷,是因為在使用TempData["key"]=value的時候,索引器的set中包含了一句,this._initialKeys.add(key)的方法) //如果不是,則不會去再次保存,這就是為什麼你在第二個請求的Action中只能取一次的原因了。 //這裡說的,是指第三次刷新第二次請求的那個Action時,已經無法訪問到第一次請求存進去的TempData中的值了,因為第二次請求的時候,因為這個key沒有在_initialKeys和_retainKeys這兩個HashSet中。 //如果要繼續保留,請在使用TempData中的數據之前,註意:是之前哦,調用Keep()或者Keep(string key)方法。
this._data.RemoveFromDictionary<string, object, TempDataDictionary>(delegate (KeyValuePair<string, object> entry, TempDataDictionary tempData) {
string item = entry.Key;
return !tempData._initialKeys.Contains(item) && !tempData._retainedKeys.Contains(item);
}, this);
tempDataProvider.SaveTempData(controllerContext, this._data);
}
為什麼會在使用一次之後,就不會在保存回Session中去了呢,如果,還不夠清楚,還可以看看,TempData的索引器,可以發現get下有一個關鍵代碼:
public object this[string key]
{
get
{
object obj2;
if (this.TryGetValue(key, out obj2))
{ //原來這邊在我們取數據的時候,將將key從_initialKeys中移除了,當TempData.Save方法被調用時,發現_data字典中的我們取數據的那個key不在這個HashSet中,所以就不會被保存了。 //如果你希望取數據了之後,又希望還能傳遞到下一個action用的話,那麼請使用Peek方法。
this._initialKeys.Remove(key);
return obj2;
}
return null;
}
set
{
this._data[key] = value; //這句說明瞭,為什麼我們使用TempData["key"]=value設置的值,可以留到下次使用的原因。
this._initialKeys.Add(key);
}
}