IsPostBack深入探討

 

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章