細說 Request[]與Request.Params[]

今天我來談一談容易被人混淆的二個集合:Request[]與Request.Params[]ajax

這二個集合我在博客【我心目中的Asp.net核心對象】中就提到過它們, 並且還給出了一個示例,並以截圖的形式揭示過它們的差異。但因爲那篇博客中有更多有價值的對象要介紹, 所以也就沒有花太多的篇幅着重介紹這二個集合。但我發現,不知道這二個集合差異的人確實太多,以致於我認爲頗有必要爲它們寫個專題來細說它們的差異了。數據庫

在ASP.NET編程中,有三個比較常見的來自於客戶端的數據來源:QueryString, Form, Cookie 。 咱們能夠在HttpRequest中訪問這三大對象, 好比,能夠從QueryString中獲取包含在URL中的一些參數, 能夠從Form中獲取用戶輸入的表單數據, 能夠從Cookie中獲取一些會話狀態以及其它的用戶個性化參數信息。 除了這三大對象,HttpRequest還提供ServerVariables來讓咱們獲取一些來自於Web服務器變量。 一般,這4個數據來源都很明確,我想沒人會混淆它們。編程

通常狀況下,若是咱們在事先就能明確知道某個參數是來源於哪一個集合,那麼直接訪問那個集合,問題也就簡單了。 然而,更常見的數據來源一般只會是QueryString, Form ,並且尤爲是當在客戶端使用Jquery的$.ajax()這類技術時, 能夠很隨意地將參數放到QueryString或者是Form中,那麼,服務端一般爲了也能靈活地應對這一現況, 可使用Request[]與Request.Params[] 這二種方式來訪問這些來自於用戶提交的數據。 本文的故事也所以而產生了:Request[]與Request.Params[] 有什麼差異??瀏覽器

回顧博客原文

因爲【我心目中的Asp.net核心對象】有對它們的一些介紹以及示例截圖, 無奈,有些人可能因爲各類緣由,沒看到那段文字,這裏我也只好再貼一次了:服務器


這二個屬性均可以讓咱們方便地根據一個KEY去【同時搜索】QueryString、Form、Cookies 或 ServerVariables這4個集合。 一般若是請求是用GET方法發出的,那咱們通常是訪問QueryString去獲取用戶的數據,若是請求是用POST方法提交的, 咱們通常使用Form去訪問用戶提交的表單數據。而使用Params,Item可讓咱們在寫代碼時沒必要區分是GET仍是POST。 這二個屬性惟一不一樣的是:Item是依次訪問這4個集合,找到就返回結果,而Params是在訪問時,先將4個集合的數據合併到一個新集合(集合不存在時建立), 而後再查找指定的結果。
爲了更清楚地演示這們的差異,請看如下示例代碼:cookie

<body>    
    <p>Item結果:<%= this.ItemValue %></p>
    <p>Params結果:<%= this.ParamsValue %></p>
    
    <hr />
    
    <form action="<%= Request.RawUrl %>" method="post">
        <input type="text" name="name" value="123" />
        <input type="submit" value="提交" />
    </form>
</body>

 

public partial class ShowItem : System.Web.UI.Page
{
    protected string ItemValue;
    protected string ParamsValue;

    protected void Page_Load(object sender, EventArgs e)
    {
        string[] allkeys = Request.QueryString.AllKeys;
        if( allkeys.Length == 0 )
            Response.Redirect("ShowItem.aspx?name=abc", true);


        ItemValue = Request["name"];
        ParamsValue = Request.Params["name"];        
    }
}

頁面在未提交前瀏覽器的顯示:併發

點擊提交按鈕後,瀏覽器的顯示:工具

差異很明顯,我也很少說了。說下個人建議吧:儘可能不要使用Params,不光是上面的結果致使的判斷問題, 不必多建立一個集合出來吧,並且更糟糕的是寫Cookie後,也會更新集合。post


正如我前面所說的客觀緣由:因爲那篇博客中有更多有價值的對象要介紹,所以也就沒有花太多的篇幅着重介紹這二個集合。 下面,我來仔細地說說它們的差異。

實現方式分析

前面的示例中,我演示了在訪問Request[]與Request.Params[] 時獲得了不一樣的結果。爲何會有不一樣的結果呢,我想咱們仍是先去看一下微軟在.net framework中的實現吧。

首先,咱們來看一下Request[]的實現,它是一個默認的索引器,實現代碼以下:

public string this[string key]
{
    get
    {
        string str = this.QueryString[key];
        if( str != null ) {
            return str;
        }
        str = this.Form[key];
        if( str != null ) {
            return str;
        }
        HttpCookie cookie = this.Cookies[key];
        if( cookie != null ) {
            return cookie.Value;
        }
        str = this.ServerVariables[key];
        if( str != null ) {
            return str;
        }
        return null;
    }
}

這段代碼的意思是:根據指定的key,依次訪問QueryString,Form,Cookies,ServerVariables這4個集合,若是在任意一個集合中找到了,就當即返回。

Request.Params[]的實現以下:

public NameValueCollection Params
{
    get
    {
        //if (HttpRuntime.HasAspNetHostingPermission(AspNetHostingPermissionLevel.Low))
        //{
        //    return this.GetParams();
        //}
        //return this.GetParamsWithDemand();

        // 爲了便於理解,我註釋了上面的代碼,其實關鍵仍是下面的調用。
        return this.GetParams();
    }
}
private NameValueCollection GetParams()
{
    if( this._params == null ) {
        this._params = new HttpValueCollection(0x40);
        this.FillInParamsCollection();
        this._params.MakeReadOnly();
    }
    return this._params;
}
private void FillInParamsCollection()
{
    this._params.Add(this.QueryString);
    this._params.Add(this.Form);
    this._params.Add(this.Cookies);
    this._params.Add(this.ServerVariables);
}

它的實現方式是:先判斷_params這個Field成員是否爲null,若是是,則建立一個集合,並把QueryString,Form,Cookies,ServerVariables這4個集合的數據所有填充進來, 之後的查詢都直接在這個集合中進行。

咱們能夠看到,這是二個大相徑庭的實現方式。也就是由於這個緣由,在某些特殊狀況下訪問它們獲得的結果將會不同。 
不同的緣由是:Request.Params[]建立了一個新集合,併合並了這4個數據源,遇到同名的key,天然結果就會不一樣了。

再談Cookie

在博客【我心目中的Asp.net核心對象】中, 說到Request.Params[]時,我簡單地說了一句:並且更糟糕的是寫Cookie後,也會更新集合。 如何理解這句話呢?

我想咱們仍是來看一下咱們是如何寫一個Cookie,併發送到客戶端的吧。下面我就COPY一段 【細說Coookie】中的一段原文吧:


Cookie寫入瀏覽器的過程:咱們可使用以下代碼在Asp.net項目中寫一個Cookie 併發送到客戶端的瀏覽器(爲了簡單我沒有設置其它屬性)。

HttpCookie cookie = new HttpCookie("MyCookieName", "string value");
Response.Cookies.Add(cookie);

代碼的關鍵點在於調用了Response.Cookies.Add(),咱們來看看它們是如何實現的。首先來看Response.Cookies

public HttpCookieCollection Cookies
{
    get
    {
        if (this._cookies == null)
        {
            this._cookies = new HttpCookieCollection(this, false);
        }
        return this._cookies;
    }
}

再來看HttpCookieCollection這個集合的實現:

public sealed class HttpCookieCollection : NameObjectCollectionBase
{
    public HttpCookieCollection() : base(StringComparer.OrdinalIgnoreCase)
    {
    }

    internal HttpCookieCollection(HttpResponse response, bool readOnly) 
        : base(StringComparer.OrdinalIgnoreCase)
    {
        this._response = response;
        base.IsReadOnly = readOnly;
    }

    public void Add(HttpCookie cookie)
    {
        if (this._response != null)
        {
            this._response.BeforeCookieCollectionChange();
        }
        this.AddCookie(cookie, true);
        if (this._response != null)
        {
            this._response.OnCookieAdd(cookie);
        }
    }

注意:因爲在HttpResponse中建立HttpCookieCollection時,把HttpResponse對象傳入了,所以,在調用HttpCookieCollection.Add()方法時, 會調用HttpResponse的OnCookieAdd(cookie); 咱們接着看:

internal void OnCookieAdd(HttpCookie cookie)
{
    this.Request.AddResponseCookie(cookie);
}

又轉到Request對象的調用了,接着看:

internal void AddResponseCookie(HttpCookie cookie)
{
    if (this._cookies != null)
    {
        this._cookies.AddCookie(cookie, true);
    }
    if (this._params != null)
    {
        this._params.MakeReadWrite();
        this._params.Add(cookie.Name, cookie.Value);
        this._params.MakeReadOnly();
    }
}

從以上的代碼分析中,咱們能夠看到,咱們調用Response.Cookies.Add()時,Cookie也會加到HttpRequest.Cookies中。
並且,若是咱們訪問過程Request.Params[],也會加到那個集合中。

再談NameValueCollection

本文一開始的示例中,爲何代碼 ParamsValue = Request.Params["name"]; 獲得的結果是:【abc,123】?

根據前面示例代碼咱們能夠得知:abc這個值是由QueryString提供的,123這值是由Form提供的,最後由Request.Params[]合併在一塊兒了就變成這個樣子了。 有沒有人想過:爲何合起來就變成了這個樣子了呢?

要回答這個問題,咱們須要回顧一下Params的定義:

public NameValueCollection Params

注意:它的類型是NameValueCollection 。MSDN對這個集合有個簡單的說明:

此集合基於 NameObjectCollectionBase 類。但與 NameObjectCollectionBase 不一樣,該類在一個鍵下存儲多個字符串值。

爲了便於你們更容易理解這個類的工做方式,我畫了一張草圖:

【name】這個key對應差一個ArrayList,而那個ArrayList中,包含了二個字符串:abc 和 123 ,這就是它的工做方式。

既然它能在一個鍵值下存儲多個字符串,那咱們就來看一下它究竟是如何實現的,直接轉到Add()方法:(注意我在代碼中添加的註釋)

public virtual void Add(string name, string value)
{
    if( base.IsReadOnly ) {
        throw new NotSupportedException(SR.GetString("CollectionReadOnly"));
    }
    this.InvalidateCachedArrays();

    // 這是一個關鍵的調用,它調用基類,獲得每一個name對應的元素,
    // 而每一個name對應的元素是一個ArrayList
    ArrayList list = (ArrayList)base.BaseGet(name);

    if( list == null ) {
        // 若是不存在name對應的元素,則建立ArrayList
        list = new ArrayList(1);
        if( value != null ) {
            // 添加value到ArrayList,它將是第一個值
            list.Add(value);
        }
        base.BaseAdd(name, list);
    }
    else if( value != null ) {
        // 在原有的ArrayList中繼續添加新的值
        list.Add(value);
    }
}

咱們再來看一下當咱們訪問Params[]這個索引器時,.net framework又是如何實現的:

public string this[string name]
{
    get { return this.Get(name); }
    set { this.Set(name, value); }
}
public virtual string Get(string name)
{
    // 根據name獲得ArrayList
    ArrayList list = (ArrayList) base.BaseGet(name);
    // 將ArrayList變成一個字符串行
    return GetAsOneString(list);
}

private static string GetAsOneString(ArrayList list)
{
    int num = (list != null) ? list.Count : 0;
    if( num == 1 ) {
        return (string)list[0];
    }
    if( num <= 1 ) {
        return null;
    }
    StringBuilder builder = new StringBuilder((string)list[0]);
    for( int i = 1; i < num; i++ ) {
        builder.Append(',');    // 逗號就來源於此。
        builder.Append((string)list[i]);
    }
    return builder.ToString();
}

如今,您該明白了爲何當一個key有多個值時,爲何會用逗號分開來了吧。

或許,看到這裏,您又有了一個新的想法:對於有多值的狀況,還要我來按逗號拆分它們,太麻煩了,有沒有不要拆分的方法呢?

答案是:有的,您能夠訪問NameValueCollection的GetValues方法,這個方法的實現以下:

public virtual string[] GetValues(string name)
{
    ArrayList list = (ArrayList)base.BaseGet(name);
    return GetAsStringArray(list);
}
private static string[] GetAsStringArray(ArrayList list)
{
    int count = (list != null) ? list.Count : 0;
    if( count == 0 ) {
        return null;
    }
    string[] array = new string[count];
    list.CopyTo(0, array, 0, count);
    return array;
}

我想結果必定是您所期待的,它是一個string[] ,咱們能夠方便的遍歷它:

string[] array = Request.Params.GetValues("name");
if( array != null )
    foreach(string val in array)
    

再談QueryString, Form

前面我解釋了NameValueCollection的工做原理,並揭示了Request.Params["name"]; 獲得【abc,123】這個結果的緣由。
事實上,這個怪異的結果有時並不僅是Params會有,一樣的故事還可能由QueryString, Form這二個對象上演(最終會在Request[]那裏也有體現)。

我仍是拿【我心目中的Asp.net核心對象】的示例來講明吧:

protected void Page_Load(object sender, EventArgs e)
{
    string[] allkeys = Request.QueryString.AllKeys;
    if( allkeys.Length == 0 )
        Response.Redirect(
            Request.RawUrl + "?aa=1&bb=2&cc=3&aa=" + HttpUtility.UrlEncode("5,6,7"), true);

    StringBuilder sb = new StringBuilder();
    foreach( string key in allkeys )
        sb.AppendFormat("{0} = {1}<br />", 
            HttpUtility.HtmlEncode(key), HttpUtility.HtmlEncode(Request.QueryString[key]));

    this.labResult.Text = sb.ToString();
}

頁面最終顯示結果以下(注意鍵值爲aa的結果):


示例代碼中,開始部分用於檢查URL是否包含參數,若是沒有,則加入一些參數。寫成這樣的緣由是: 第一次訪問這個頁面時,URL中確定是不包含參數的,爲了能演示,因此我就加了一些固定的參數,這樣便於後面的講解。

這個示例也演示了:遇到同名的多個值,用逗號分開的作法,並非Params纔會有的,QueryString也可能會有, 固然,Form也逃不掉這個特性的纏繞,不過,我如今倒想舉個有使用價值的示例。

我有這樣一個錄入界面(左邊),並但願最終的錄入結果以右圖的方式顯示:

我想這個功能的實現並不難,但如何作纔是最簡單呢?

下面我貼出個人實現方法,你們看看算不算比較容易:

    <tr><td style="vertical-align: top">項目類型</td><td>
    <% foreach( string pt in AppHelper.ProjectTypes ) { %>
        <label><input type="checkbox" name="ProjectType" value="<%= pt %>" /><%= pt%></label><br />
    <% } %>
        </td></tr>

注意:全部的checkbox的name都是同樣的。

服務端嘛,我認爲沒有必要再貼代碼了,我想您懂的。

在這個示例中,我正好利用了NameValueCollection的這個特色,讓它幫我實現了這個逗號分隔的效果,要否則,我還得本身去作!

如何處理衝突

經過前面的一些示例,咱們能夠看到並不是只有Params[]會有衝突,只要類型是NameValueCollection的數據源,都有這個問題。
我要再重申一次:QueryString, Form,Param都有這樣的衝突問題,只是Param須要合併4種數據源,它將衝突的機率變大了。

那麼,咱們如何正確的處理這類衝突呢? 還記得我前面提到的NameValueCollection的GetValues方法吧,也只好用它了。(除非你願意本身手工拆分字符串) 而後再用一個循環就能夠訪問全部衝突值了,就像下面這樣:

string[] array = Request.Params.GetValues("name");
if( array != null )
    foreach(string val in array)
    

注意:Request[]的返回結果是一個字符串,就不能使用這種方法。可是,它的衝突機率要少不少。

如今,還有個現實的問題:QueryString, Form是最經常使用的數據源,咱們要不要這樣處理它呢?
這的確是個很現實的問題,我認爲在回答這個問題前,咱們須要分析一下這二個集合出現KEY衝突時是個什麼樣子的。

1. "abc.aspx?id=1 &id=2" 在這URL中,我想問問各位:看到這個URL,您會怎麼想?我認爲它是錯的,錯在拼接URL的操做中。 其次,我認爲URL的修改或者拼接一般由一個工具類來控制,咱們有理由保證它不會出現衝突,畢竟範圍相應較小,咱們容易給出這個保證。 所以,我認爲,直接訪問QueryString能夠忽略這種衝突的可能。

2. 表單數據中name重複的狀況。我認爲這個集合卻是有可能出現衝突,但也極有多是咱們故意安排的,就像前面的示例同樣。 其次,表單的內容在開發階段相對固定,各個輸入控件的name也是比較清楚的,不存在動態變換的可能,所以, 我認爲,直接訪問Form也能夠忽略這種衝突的可能。

另外一方面,咱們平時寫QueryString[], Form[]都太習慣了,這樣的代碼太多了,也不可能改爲循環的判斷, 所以,咱們只能儘可能保證在一個數據源的範圍內,它們是不重複的。 事實上,前二個集合一般僅僅與某一個頁面相關,範圍也相對較小,更容易保證。 所以,若是有了這個保證,在訪問這二類集合時,忽略衝突也是可接受的。

可是,Params須要合併4種數據源,尤爲是包含Cookies,ServerVariables這二類對象並不是與某個頁面相關,它們是全局的, 所以衝突的可能性會更大,因此,我認爲:若是您要訪問Params[],那麼,請改爲Params.GetValues() ,由於這樣才更合適。

Request[]仍是Request.Params[] ??

前面說了這麼多,我想Request[]和Request.Params[]的差異,此次應該是說清楚了,到此也該給個結論了: 到底選擇Request[]仍是Request.Params[] ??

我想不少人應該會比較關注這個答案,這裏我也想說說個人觀點,不過,我要說明一點: 本文的全部文字,都只表示個人我的觀點,僅供參考。

我認爲:要想清楚地回答這個問題,有必要從二個角度再來觀察這兩者:常見用法和設計緣由。

1. 常見用法: 我一直認爲設計這二個集合是爲了方便,讓咱們能夠沒必要區分GET, POST而直接獲得所需的用戶數據。 若是沒有這二個集合,而咱們又須要不區分GET,POST時,顯然就要本身去實現這類的集合, 而在本身實現時,也極有可能是先嚐試訪問QueryString, 若是沒有,再去找Form ...。看到了嗎,這不正是Request[]的實現方式嗎? 也正是基於這個前提,遇到前面那種【abc,123】場景時,Request[]獲得的結果或許更符合咱們的預期。畢竟在獲取到結果後, 咱們會基於結果作一些判斷,好比:name參數可能對應一個數據庫的表字段,用它能夠找到一個實際數據行, 若是結果是abc或者是123,這時程序都能處理(都有匹配的記錄),但來個【abc,123】,這個還真無法處理了。

另外一方面,在前面的例子中,我也解釋了這並非Params[]特有的,QueryString, Form都有這樣的問題,天然地Request[]也有這個問題, 只是因爲Params須要合併4類數據源,讓這種衝突的機會更大了

說到這裏,有必要再來談談前面的幾個例子,【abc,123】中,name在QueryString, Form中重複了,顯然這屬於不合理的設計, 現實狀況中,應該是不會產生這類事情的,除非偶然。不過,當偶然的不幸發生時,也正好能體現這兩者的差異了。 至於我前面所舉的幾個例子,雖然在現實中不太可能會出現,但個人意圖是在向您展現這些技術的細節差別, 展現一些可能偶然會發生的狀況,所以,請不要認爲那是個技術誤導。

2. 設計緣由:讓咱們再從設計嚴謹性這個角度來看待這兩者的差異,仍是拿【abc,123】這個例子來講吧, Request[]這種依次判斷的方式,顯然會遺漏一些信息,所以,從嚴謹性這個角度來看,Request[]是不完美的。 畢竟,最終用戶會如何以某種想法使用這二個API,沒人知道。微軟是設計平臺的,他們不得不考慮這個問題,不設計這二個集合, 是.net framework的不完善,用錯了,就是咱們本身的錯了。

基於以上觀點,我給出個人4點意見:1. 不建議使用Params[],由於:a. 不但願被偶然狀況影響,b. 較高的使用成本。2. Request[] 使用簡單,適合要求不高的場合:示例代碼。3. 若是須要兼顧性能和易用性,能夠模仿Request[]自行設計。(一般並不須要訪問4個集合)4. 瞭解差別,體會細節,有時會發現它仍是有利用價值的。

相關文章
相關標籤/搜索