自制一個可編輯QueryString的類URLModifier

有些狀況下,須要 新增/刪除/替換 url中的部分Querystring中的參數,而.net自帶的Uri類只能解析,不能編輯,,而且若是是Relative類型的連接,轉成Uri類型以後,不少參數又不能很好的讀取,所以,,,本身動手,豐衣足食,,你們不用複製文內的代碼,,,文章最後,給出完整的類的代碼連接,,有須要的,直接複製就好了html

 

使用的場景如:前端

當前request的url=  https://www.xxx.com/doc/base/infrastructure.html?categoryid=5&order=2&state=2u#topoopgit

在前端razor輸出的時候,須要保留order和state的狀況下.修改categoryid值:github

    @{
        var requestUrlModifier = ViewContext.HttpContext.Request.GetDisplayUrl().ToUrlModifier();
    }
    @foreach (var category in Model.Categories)
    {
        <a href="@requestUrlModifier.Clone().ReplaceQuery("categoryID", category.CategoryID.ToStringEx()).ToString()"></a>
    }

 

首先明確這個工具類的幾個做用正則表達式

1,可 新增/刪除/替換 連接中的QueryString 中的各個keyvalueide

2.可 替換/設置/保留 錨點anchor函數

3.對原有的Host/端口/協議/路徑 保留原有值工具

4.最後可輸出編輯後的結果oop

5.對不太標準的Url或部分有問題的QueryString支持測試

 

測試主要如下兩種Url:

1.https://www.layui.com/doc/base/infrastructure.html?=&o==ppp&&pppp=iii&o1=ooo&test2=&u#topoop

  這個連接QueryString中,除了正常的KeyValue對以外,,還有出現:

  1) =& 開頭的無效字符

  2) o==ppp 中間包含兩個==號的錯誤連續

  3) &&pppp=iii 出現 && 兩個連續字符

  4) test2=& 這樣的只有key,沒有value

  5) &u 這樣的只有key,連等號都沒有的

  6) #topoop 連接後的錨點

2.https://www.layui.com/doc/base/infrastructure.html?=&o==ppp&&pppp=iii&o1=ooo&test2=&

  1) 連接最後以 & 結尾

3. /doc/base/infrastructure.html?=&o==ppp&&pppp=iii&o1=ooo&test2=&u#topoop

  1) 連接以 / 開頭的相對連接,不包含域名信息和端口等信息

若是還有其餘狀況,能夠留言,增長測試用例

因爲該類在實際使用過程當中,可能會頻繁被調用,所以,須要儘可能少的消耗資源,儘可能少的變量定義

處理的流程主要分如下幾個步驟:

1.從後往前,計算出是否包含錨點,並計算錨點所在的index1

2.從前日後,計算出?號所在index2

3.截取出從0到index2的全部字符

4.從index2到錨點index1之間的字符串就是QueryString了

5.循環判斷QueryString的起始和結束index,爲了去掉先後的無效字符

6.循環獲取=和&號的位置,切分字符串

到此,完成對Url字符串的解析,,這裏爲何不用正則表達式呢,,,由於第一慢,第二閒,,沒事本身寫,省事省空間

 

接下來就上代碼了,,每一個處理步驟,請看代碼的註釋文字

1.在構造函數中,對傳入的Url進行解析操做

  private string _anchor = string.Empty;
    private string _hostAndPath =string.Empty;
    private List<(string key, string value)> _queryKeys = new List<(string key, string value)>(5);


    public UrlModifier(string url) //直接傳入string,不用uri,省的相對路徑下,Uri類在讀取Query屬性的以後報錯
    {
        if (string.IsNullOrWhiteSpace(url))
        {
            return;
        }

        var endIndex = url.Length - 1;

        //從後往前掃描錨點#
        for (int i = url.Length-1; i >= 0; i--)
        {
            if (url[i]=='#')
            {
                _anchor = url.Substring(i+1);
                endIndex = i - 1;
                break;
            }

            //防止出現無#字符的狀況
            if (url[i] == '=' || url[i] == '&' || url[i] == ' ' || url[i] == '?' || url[i]=='/')
            {
                endIndex = url.Length-1;
                break;
            }
        }

        //截取域名段,包含協議,端口號等
        var hostEndIndex = endIndex;
        for (int i = 0; i < endIndex; i++)
        {
            if (url[i]=='?')  //查找?號所在index
            {
                hostEndIndex = i - 1;
            }
        }

        if (hostEndIndex>0)  //若是是絕對路徑的,獲取Host和Path的字符串
        {
       if(url[hostEndIndex]=='?')
       {
         _hostAndPath=url.Substring(0,hostEndIndex); 
       }
       else
       {
         _hostAndPath=url.Substring(0,hostEndIndex+1);
       }
hostEndIndex++; } if (hostEndIndex >0 && hostEndIndex < endIndex) { //排除掉使用=號或者&或者空格結尾的字符,減小後續判斷的麻煩,計算出實際的結束index for (int i = endIndex; i >= hostEndIndex; i--) { var c = url[i]; if (c != '=' && c != '&' && c != ' ') { endIndex = i; break; } } var keyword = ""; var value = ""; var startIndex = 1; char lastKeyword ='\0' ; for (int i = hostEndIndex; i < endIndex; i++) { var c = url[i]; //排除掉使用 = 號或者 & 或者 ? 或者空格開頭的字符,減小後續判斷的麻煩,計算出實際起始index if (c != '=' && c != '&' && c != ' ' && c!='?') { startIndex = i; break; } } if (startIndex>=endIndex) //若是沒字符了,整個都是特殊字符,則直接返回 { return; }
       //接下來就是解析QueryString的過程了,比較繁瑣,具體就是查找=和&符號,並截取中間的字符串做爲key和value
for (int i = startIndex; i <= endIndex; i++) { var c = url[i]; if (c == '=') { if (lastKeyword=='=') //處理 ?s==0 { startIndex=i+1; continue; } keyword = url.Substring(startIndex, i - startIndex); lastKeyword = c; startIndex = i+1; //startIndex++; } if (c == '&') { if (url[i-1] == '&') //處理 ?s=0&& 的狀況 { startIndex = i + 1; continue; } if (lastKeyword=='=' || lastKeyword=='\0') // 處理 ?ss=0 的狀況 { value = url.Substring(startIndex , i-startIndex); } // 處理 ?ddd& 或者 ?p=0&ddd 這種狀況,切分出來的,算key else if (lastKeyword=='&' || lastKeyword =='\0') { keyword = url.Substring(startIndex, i - startIndex); value = string.Empty; } lastKeyword = '\0'; startIndex = i + 1; if (!string.IsNullOrEmpty(keyword)) { AddQuery(keyword, value); //添加入列表 } } } //若是還有剩餘的字符,則處理完剩下的字符串 if (startIndex <= endIndex) { if (lastKeyword=='=' && !string.IsNullOrEmpty(keyword)) //處理 ?d=value 的狀況 { value = url.Substring(startIndex,endIndex - startIndex); } else if ((lastKeyword == '=' && string.IsNullOrEmpty(keyword)) || lastKeyword == '&' || lastKeyword == '\0') { keyword = url.Substring(startIndex, endIndex+1 - startIndex); value = string.Empty; } AddQuery(keyword, value); } } }

2.增長几個處理函數

    /// <summary>
    /// 添加一對參數值
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public UrlModifier AddQuery(string key, string value)
    {
        //若是存在相同的Key的,則直接附加到原有值的後面
        var index = _queryKeys.IndexOf(x => x.key.CompareTo(key, true));

        if (index>0)
        {
            var orgValue = _queryKeys[index].value;
            _queryKeys[index] = (key, $"{orgValue},{value}");
        }
        else
        {
            _queryKeys.Add((key, value));
        }

        return this;
    }

    /// <summary>
    /// 添加一個keyvalue
    /// </summary>
    /// <param name="pair"></param>
    /// <returns></returns>
    public UrlModifier AddQuery((string key, string value) pair)
    {
        return AddQuery(pair.key, pair.value);
    }

    /// <summary>
    /// 刪除指定key的項目
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public UrlModifier RemoveQuery(string key)
    {
        _queryKeys.Remove(x => x.key == key);

        return this;
    }

    /// <summary>
    /// 替換指定key的數據
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public UrlModifier ReplaceQuery(string key, string value)
    {
        var index = _queryKeys.IndexOf(x => x.key == key);

        if (index < 0)
        {
            _queryKeys.Add((key, value));
        }
        else
        {
            _queryKeys[index] = (key, value);
        }

        return this;
    }

    /// <summary>
    /// 設置錨點值
    /// </summary>
    /// <param name="anchor"></param>
    /// <returns></returns>
    public UrlModifier SetAnchor(string anchor)
    {
        _anchor = anchor;
        return this;
    }
View Code

3.重載ToString函數,用於拼接並輸出操做後的結果

  public override string ToString()
    {
        var sb=new StringBuilder(60);

        if (!string.IsNullOrEmpty(_hostAndPath))
        {
            sb.Append(_hostAndPath);
        }

        if (_queryKeys!=null && _queryKeys.Count>0)
        {
            sb.Append('?');

            foreach (var item in _queryKeys)
            {
                sb.Append(item.key);

                if (!string.IsNullOrEmpty(item.value))
                {
                    sb.Append('=');
                    sb.Append(item.value);
                    
                }
                sb.Append('&');
            }

            if (sb[sb.Length-1]=='&')
            {
                sb.Remove(sb.Length - 1, 1);
            }
            
        }

        if (!string.IsNullOrEmpty(_anchor))
        {
            sb.Append('#');
            sb.Append(_anchor);
        }

        return sb.ToString();
    }

4.固然,爲了能節省解析的次數,畢竟若是是循環內修改參數的,那麼基本上基礎的url是相同的,所以,咱們能夠實現一個Clone,讓解析的結果能夠做爲模板複用

    public UrlModifier Clone()
    {
        var item=new UrlModifier("") ;

        item._queryKeys.AddRange(this._queryKeys);
        item._hostAndPath = this._hostAndPath;
        item._anchor = this._anchor;

        return item;
    }

5.爲了方便開發,其實還能夠再增長一些擴展函數,好比直接從Url的string轉爲URLModifier,或者增長不一樣的隱式轉換的函數,具體可參考文章最後的源碼文件

6.調用測試:

    public void TestUrlModifier()
    {
        var url = "https://www.xxx.com/doc/base/infrastructure.html";
        var newUrl = url.ToUrlModifier().SetAnchor("topoop").RemoveQuery("keyword").ReplaceQuery("pppp", "iii")
            .AddQuery("o1", "ooo");
        var newUrlStr = newUrl.ToString();

        var test5 = "https://www.xxx.com/doc/base/infrastructure.html?=&o==ppp&&pppp=iii&o1=ooo&test2=&u#topoop"; //出現連續兩個=號及連續的&號
        var newUrl5 = test5.ToUrlModifier();
        var newStr5 = newUrl5.ToString();

        var test6 = "/doc/base/infrastructure.html?=&o==ppp&&pppp=iii&o1=ooo&test2=&u#topoop"; //出現連續兩個=號及連續的&號,而且是相對路徑
        var newUrl6 = test6.ToUrlModifier();
        var newStr6 = newUrl6.ToString();
    }

輸出的結果爲:

newUrlStr:  https://www.xxx.com/doc/base/infrastructure.html?pppp=iii&o1=ooo#topoop

newStr5: https://www.xxx.com/doc/base/infrastructure.html?o=ppp&pppp=iii&o1=ooo&test2&u#topoop   //排除掉錯誤的格式,並格式化輸出

newStr6: /doc/base/infrastructure.html?o=ppp&pppp=iii&o1=ooo&test2&u#topoop     //輸出原始的相對路徑 並排除錯誤格式

 

注意:該類並不會對QueryString中的Key或者Value進行編碼,所以,有須要編碼的,可增長一個AddQuery的重載,進行處理

 

完整的類的代碼在:https://github.com/kugarliyifan/Kugar.Core/blob/master/Kugar.Core.NetCore/Network/UrlModifier.cs 稍做修改就能夠單獨使用

AddQuery函數中使用到了一個IndexOf的擴展方法,在:https://github.com/kugarliyifan/Kugar.Core/blob/master/Kugar.Core/ExtMethod/ListExtMethod.cs#L1193 

相關文章
相關標籤/搜索