1 IsPostBack 介紹
IsPostBack是 Page類有一個 bool類型的屬性,用來判斷針對當前 Form的請求是第一次還是非第一次請求。當 IsPostBack= true時表示非第一次請求,我們稱爲 PostBack,當 IsPostBack= false時表示第一次請求。在 asp.net框架內部有很多的場景需要判斷 IsPostBack,比如 LoadAllState等操作就需要在 PostBack的時候進行。對於我們自己使用 WebForm進行開發時,經常會在 Page_Load中對 IsPostBack進行判斷,因爲第一次請求的時候會執行 Page_Load,在非第一次請求的時候也會執行 Page_Load。爲什麼對同一個 Form有多次請求呢? asp.net中引入了服務器端事件,支持服務器端事件的控件,會發出對當前 Form的請求,這樣在很多情形下我們就需要區別是否是對這個 Form的第一次請求。
2 IsPostBack 結論
本人對 .Net的源代碼中相關的處理進行的分析得到如下的結論:
結論① 對於使用 Server.Transfer進行遷移時遷移到的頁面其 IsPostBack= false。
結論② Post方式如果 Request中沒有請求值,即 Request.Form =null則 IsPostBack= false; Get方式如果 Request中沒有請求值,即 Request.QueryString =null則 IsPostBack= false。
結論③ 如 果QueryString或Form雖然有請求值,但是QueryString或Form中的Key沒有“__VIEWSTATE”和 “__EVENTTARGET”和“__VIEWSTATEFIELDCOUNT”,並且沒有鍵爲“null”,值以“__VIEWSTATE”開頭並且 也沒有值爲“__EVENTTARGET”的鍵值對,則IsPostBack=false。
結論④ 使用 Response.Redirect方式向自畫面遷移時,此時 IsPostBack =false。
結論⑤ 發生跨頁提交( CrossPagePostBack),當訪問 PreviousPage屬性的時候,對於源 Page, IsPostBack=true。
結論⑥ 發生跨頁提交( CrossPagePostBack)時目標頁面是 IsPostBack= false
結論⑦ 使用 Server.Execute遷移到的頁面其 IsPostBack= false。
結論⑧ 在 Page運行期間其對應的 DLL被更新了並且 Page的樹結構發生過變化,這種情況下請求時 IsPostBack= false。
可以這樣來理解這些結論:一般情況判斷 Request中如果沒有請求值則 IsPostBack= false。如果有請求值但是不包括 “__VIEWSTATE”等一些特殊的鍵或值,則 IsPostBack= false(每次請求後 .Net框架會將一些特殊的隱藏域“ __VIEWSTATE ”等返回給客戶端)。還有一些特殊的情形是上面的規則不能正確判斷的需要特殊處理的,這些情形包括 Server.Transfer, Response.Redirect, CrossPagePostBack, Server.Execute,發生了頁面元素變化及重新編譯。
一般來說記住上面的結論就可以,如果您有興趣,或者懷疑請繼續看下面的 IsPostBack推論過程。
3 IsPostBack 推論過程
下面是根據 .Net框架中的源代碼,來分析 IsPostBack是如何判斷出來的。對於這些結論的推斷本人做了相關的試驗來證明推論的正確性,由於篇幅的原因沒有將這些試驗代碼體現出來。另外不可能將全部的 .Net框架的代碼都體現出來,只是將相關的代碼片段列出,說明推斷的依據。另外由於本人水平有限對 .Net框架的代碼理解還存在的不足的地方,請發現後進行指正,謝謝。
public bool IsPostBack
{
get
{
if (this._requestValueCollection == null )
{
return false ;
}
if (this ._ isCrossPagePostBack )
{
return true ;
}
if (this _pageFlags [8])
{
return false ;
}
return (
(
(this.Context .ServerExecuteDepth <= 0 ) ||
((this.Context .Handler != null ) &&
(base.GetType () == this .Context .Handler .GetType ()))
) && !this._ fPageLayoutChanged
);
}
}
我們將每一個 if判斷作爲一個小節,作如下的分析。
3.1 this._requestValueCollection == null
if (this._requestValueCollection == null )
{
return false ;
}
可以看出 _requestValueCollection等於 null時 IsPostBack就等於 false。
在 Page.ProcessRequestMain(bool , bool ) 中有如下的代碼:
if (this .PageAdapter != null )
{
this ._requestValueCollection = this .PageAdapter.DeterminePostBackMode();
}
else
{
this ._requestValueCollection = this .DeterminePostBackMode();
}
PageAdapter.DeterminePostBackMode 最終還是調用了 Page.DeterminePostBackMode ,下面我們看 Page.DeterminePostBackMode 如何實現。
protected internal virtual NameValueCollection DeterminePostBackMode()
{
if (this .Context.Request == null )
{
return null ;
}
if (this .Context.PreventPostback)
{
return null ;
}
NameValueCollection collectionBasedOnMethod = this .GetCollectionBasedOnMethod(false );
if (collectionBasedOnMethod == null )
{
return null ;
}
bool flag = false ;
string [] values = collectionBasedOnMethod.GetValues((string ) null );
if (values != null )
{
int length = values.Length;
for (int i = 0; i < length; i++)
{
if (values[i].StartsWith("__VIEWSTATE" , StringComparison .Ordinal) ||
(values[i] == "__EVENTTARGET" ))
{
flag = true ;
break ;
}
}
}
if (((collectionBasedOnMethod["__VIEWSTATE" ] == null ) && (collectionBasedOnMethod["__VIEWSTATEFIELDCOUNT" ] == null )) && ((collectionBasedOnMethod["__EVENTTARGET" ] == null ) && !flag))
{
return null ;
}
if (this .Request.QueryStringText.IndexOf(
HttpResponse .RedirectQueryStringAssignment, StringComparison .Ordinal) != -1)
{
collectionBasedOnMethod = null ;
}
return collectionBasedOnMethod;
}
這個函數中返回 null就意味者 IsPostBack= false,將上面函數中每個返回爲 null的地方作如下的分析。
3.1.1 this.Context.Request == null
if (this . Context.Request == null)
{
return null
;
}
this.Context.Request == null 應該只有在異常的情況下會發生,正常情況下會在 HttpRuntime.ProcessRequestInternal 中創建 HttpContext及 HttpRequest對象。
3.1.2 this.Context.PreventPostback
if (this . Context .PreventPostback)
{
return null
;
}
在 HttpServerUtility.Transfer 中會使用 PreventPostback ,其代碼如下:
public void Transfer(string path)
{
bool preventPostback = this ._context.PreventPostback;
this ._context.PreventPostback = true ;
this .Transfer(path, true );
this ._context.PreventPostback = preventPostback;
}
在調用 Server.Transfer進行畫面遷移時設置 Context.PreventPostback = ture。此處得出結論①:對於使用 Server.Transfer進行遷移時遷移到的頁面其 IsPostBack= false。
3.1.3 collectionBasedOnMethod == null
NameValueCollection collectionBasedOnMethod = this .GetCollectionBasedOnMethod(false );
if (collectionBasedOnMethod == null )
{
return null ;
}
調用了 Page.GetCollectionBasedOnMethod 後其返回值進行判斷。如果其返回值爲 null 則 IsPostBack 爲 false 。 Page.GetCollectionBasedOnMethod 的定義如下:
internal NameValueCollection GetCollectionBasedOnMethod(bool dontReturnNull)
{
if (this ._request.HttpVerb == HttpVerb .POST)
{
if (!dontReturnNull && !this ._request.HasForm)
{
return null ;
}
return this ._request.Form;
}
if (!dontReturnNull && !this ._request.HasQueryString)
{
return null ;
}
return this ._request.QueryString;
}
從上面的代碼可以看出返回值爲 null的情形是 _request.HasForm= null或 _request.HasQueryString= null。此處得出結論②: Post方式如果 Request中沒有請求值,即 Request.Form =null則 IsPostBack= false; Get方式如果 Request中沒有請求值,即 Request.QueryString =null則 IsPostBack= false。
3.1.4 ((collectionBasedOnMethod["__VIEWSTATE"] == null) && (collectionBasedOnMethod["__VIEWSTATEFIELDCOUNT"] == null)) && ((collectionBasedOnMethod["__EVENTTARGET"] == null) && !flag)
bool flag = false ;
string [] values = collectionBasedOnMethod.GetValues((string ) null );
if (values != null )
{
int length = values.Length;
for (int i = 0; i < length; i++)
{
if (values[i].StartsWith("__VIEWSTATE" , StringComparison .Ordinal) ||
(values[i] == "__EVENTTARGET" ))
{
flag = true ;
break ;
}
}
}
上面這段代碼的意思是判斷請求的鍵值對中是否存在沒有鍵,其值以“__VIEWSTATE”開頭或者其值爲“__EVENTTARGET”。例如如下的Get請求方式會使得flag=true。
…/defalt.aspx?__VIEWSTATE
…/defalt.aspx?__EVENTTARGET
對於Get方式“?__VIEWSTATE=”會將__VIEWSTATE作爲請求的鍵,其值爲“”,但是“?__VIEWSTATE”會認爲其鍵爲“null”,其值爲“__VIEWSTATE”
if (
((collectionBasedOnMethod["__VIEWSTATE"] == null ) && (collectionBasedOnMethod["__VIEWSTATEFIELDCOUNT"] == null )) && ((collectionBasedOnMethod["__EVENTTARGET"] == null ) && !flag))
{
return null ;
}
如上的條件意 味着請求的鍵中同時沒有“__VIEWSTATE”,“__EVENTTARGET”,“__VIEWSTATEFIELDCOUNT”,並且flag爲 false則返回null。flag爲false意味着沒有鍵爲“null”值以“__VIEWSTATE”開頭並且也沒有值爲 “__EVENTTARGET”的鍵值對。
此 處得出結論③如果QueryString或Form雖然有請求值,但是QueryString或Form中的Key沒有“__VIEWSTATE”和 “__EVENTTARGET”和“__VIEWSTATEFIELDCOUNT”,並且沒有鍵爲“null”值以“__VIEWSTATE”開頭並且也 沒有值爲“__EVENTTARGET”的鍵值對,則IsPostBack=false。
3.1.5 this.Request.QueryStringText.IndexOf(HttpResponse.RedirectQueryStringAssignment, StringComparison.Ordinal) != -1
if (this .Request.QueryStringText.IndexOf(HttpResponse .RedirectQueryStringAssignment, StringComparison .Ordinal) != -1)
{
collectionBasedOnMethod = null ;
}
HttpResponse.RedirectQueryStringAssignment 的 值爲“__redir=1”,上面的代碼的意思是如果QueryStringText中包括包括“__redir=1”則返回null。在 HttpRequest.Redirect中會判斷如果IsPostBack爲true,並且URL中不包含有“__redir=1”時,會給URL中增 加“__redir=1”。一般情況下我們使用request.Redirect遷移到的頁面都應該是IsPostBack=false,有一種特殊的情 形是使用request.Redirect遷移到當前頁,此時IsPostBack爲true。此種情況發生時在request.Redirect中給 URL中增加“__redir=1”。執行到page. ProcessRequestMain時會重新將IsPostBack判斷爲fales。
此處得出結論④ 使用 Response.Redirect方式向自畫面遷移時,此時 IsPostBack =false。
此時大家可能會有疑問爲什麼 使用 Response.Redirect方式向自畫面遷移時要特殊處理,使用 Response.Redirect向其他畫面遷移爲什麼不要。使用 Response.Redirect 向 其他畫面遷移時Response.Form=null,Response.QueryString=null,所以可以判斷是 IsPostBack=false。但是使用Response.Redirect方式向自畫面遷移時 Response.QueryString<>null,所以要特殊判斷。
3.2 this._isCrossPagePostBack
if (this ._isCrossPagePostBack )
{
return true ;
}
在Page的PreviousPage屬性中會對_isCrossPagePostBack進行設置,具體代碼如下:
public Page PreviousPage
{
get
{
…
ITypedWebObjectFactory vPathBuildResult = (ITypedWebObjectFactory ) BuildManager .GetVPathBuildResult(this .Context, this ._previousPagePath);
if (typeof (Page ).IsAssignableFrom(vPathBuildResult.InstantiatedType))
{
this ._previousPage = (Page ) vPathBuildResult.CreateInstance();
this ._previousPage._isCrossPagePostBack = true ;
this .Server.Execute(this ._previousPage, TextWriter .Null, true , false );
}
}
return this ._previousPage;
}
}
在發生跨頁面提交的時候,當訪問PreviousPage屬性的時候源Page的IsCrossPagePostBack會被設置true。此處得出結論⑤ 發生跨頁提交( CrossPagePostBack),當訪問 PreviousPage屬性的時候,對於源 Page, IsPostBack=true。
3.3 this._pageFlags[8]
if (this . _pageFlags [8])
{
return false ;
}
在Page. ProcessRequestMain中有如下的代碼片斷對_pageFlags[8]進行賦值。
else if (!this .IsCrossPagePostBack)
{
VirtualPath path = null ;
if (this._requestValueCollection ["__PREVIOUSPAGE" ] != null )
{
try
{
path = VirtualPath .CreateNonRelativeAllowNull(
DecryptString(this._requestValueCollection ["__PREVIOUSPAGE" ]));
}
catch (CryptographicException )
{
this ._pageFlags[8] = true ;
}
if ((path != null ) && (path != this .Request.CurrentExecutionFilePathObject))
{
this ._pageFlags[8] = true ;
this ._previousPagePath = path;
}
}
}
解密發生異常 時_pageFlags[8]爲true這種異常發生的可能性比較小我們忽略,重點看另外一種情形,將這種情形的所有條件結合起來就是 IsCrossPagePostBack=false && _requestValueCollection["__PREVIOUSPAGE"] != null && path != null && (path != this.Request.CurrentExecutionFilePathObject)。 發生跨頁提交時對於目標頁面 IsCrossPagePostBack =false, 此時源頁面的"__PREVIOUSPAGE"等信息會提交給目標頁面,所以 _requestValueCollection["__PREVIOUSPAGE"] != null。此時當前請求的CurrentExecutionFilePathObject是根據目標頁的路徑生成的,與使用 _requestValueCollection["__PREVIOUSPAGE"]生成的path對象不同。
此處得出結論⑥ 發生跨頁提交( CrossPagePostBack)時目標頁面是 IsPostBack= false。爲什麼需要對 CrossPagePostBack的目標頁面做這樣的處理呢?發生 CrossPagePostBack時,會將源頁面的信息提交給目標頁面此時 Request.Form!= null,而且包括 __VIEWSTATE 等鍵按照其他的規則會判斷爲 IsPostBack= true,所以需要對 CrossPagePostBack的目標頁面做特殊的判斷。
3.4 (this.Context.ServerExecuteDepth <= 0) || ((this.Context.Handler != null) && (base.GetType() == this.Context.Handler.GetType()))
在 HttpServerUtility中有如下的代碼對 Context. ServerExecuteDepth進行了操作。
public void Execute(string path, TextWriter writer, bool preserveForm)
{
…
try
{
this ._context.ServerExecuteDepth++;
handler = this ._context.ApplicationInstance.MapHttpHandler(this ._context, request.RequestType, path3, filename, useAppConfig);
}
finally
{
this ._context.ServerExecuteDepth--;
}
…
}
在 HttpServerUtility.ExecuteInternal中也有一處對 Context.ServerExecuteDepth類似的操作。 HttpServerUtility.Execute會調用 HttpServerUtility.ExecuteInternal。從此可以看出 Context.ServerExecuteDepth是表示 Server.Execute中的執行深度。在調用 Server.Execute時 Context.ServerExecuteDepth>0。另外調用 Server.Execute後 Context.Handle中存儲的還是原來的頁對象,也就是說 base.GetType()!= this.Context.Handler.GetType()。這樣對於 Server.Execute來說 this.Context.ServerExecuteDepth <= 0) || ((this.Context.Handler != null)這個條件爲 false。 此處得出結論⑦ 使用 Server.Execute遷移到的頁面其 IsPostBack= false。此處我們會有疑問,爲什麼需要對 Server.Execute進行特殊的判斷呢?理由是使用 Server.Execute時會將源 Page中的隱含域提交,此時 Request.Form!=null,而且包括 __VIEWSTATE 等鍵按照其他的規則會判斷爲 IsPostBack= true。
3.5 this._fPageLayoutChanged
fPageLayoutChanged從這個變量的字面意思來看是 Page的 Layout發生了變化。
在 Page.LaodAllState中代碼片斷如下:
private void LoadAllState()
{
…
string s = (string ) second.First;
int num = int .Parse(s, NumberFormatInfo .InvariantInfo);
this ._fPageLayoutChanged = num != this .GetTypeHashCode();
…
}
其意思是現在得到的 HashCode 和存儲在 ViewState 中的 HashCode 不一致時 fPageLayoutChanged = true 。 GetTypeHashCode() 會返回一個 HashCode ,而且這個方法是對 aspx 進行編譯的時候產生的,只有在頁面上的元素髮生了變化的時候其返回的值會發生變化。 此處得出結論⑧ 在 Page 運行期間其對應的 DLL 被更新了並且 Page 的樹結構發生過變化,這種情況下請求時 IsPostBack = false 。