前陣子談過用 HttpUtility.ParseQueryString 解析、修改及還原參數的簡便做法,一不做二不休,再來聊聊如果拿到一個 URL, HTTP 要改 HTTPS、主機名稱要換、Port、路徑要改,是不是也有不走字串比對置換的優雅做法?
爬文查到好用的 .NET 內建物件 - UriBuilder(.NET 2.0 時代就有了,我到現在才學會),Scheme(http、https)、Port、Host、Path 均可任意抽換調整,再產生新的 URL,比起自己寫 Regex 比對置省事可靠不少。UriBuilder 已滿足大部分所需,只少了將 QueryString 轉成 NameValueColletion 方便抽換修改參數,查了文件 UriBuilder 並未宣告 seal 禁止繼承,因此繼承 UriBuilder 再擴充參數處理功能是不錯的選擇。
我寫了一顆 FlexUrlEditor 繼承 UriBuilder,建構式傳入既有 URL,融和上回將 QueryString 轉為 NameValueCollection 增刪修改再轉回字串的技巧,參數可透過 FlexUrlEditor["參數名"] 存取,為 UriBuilder 加上編修 QueryString 參數功能。此外,UriBuilder 產出的 URL 字串有個小缺點 - 它包含 :80、:443 等習慣上會省略的 Port,所以我在輸出時加了點工,使結果更符合平日慣用格式。程式碼如下:
using System;
using System.Collections.Specialized;
using System.Text;
using System.Web;
public class FlexUrlEditor : UriBuilder
{
private readonly NameValueCollection collection;
public FlexUrlEditor(string url) : base(url)
{
collection =
HttpUtility.ParseQueryString(new Uri(url).Query, Encoding.UTF8);
}
public string this[string key]
{
get { return collection[key]; }
set
{
if (value == null && collection[key] != null)
{
collection.Remove(key);
}
else
{
collection[key] = value;
}
}
}
public string GenUrl()
{
//REF: https://goo.gl/gHmGUz
base.Query = Uri.EscapeUriString(
HttpUtility.UrlDecode(collection.ToString()));
//remove 80/443 port for http/https
if (Scheme == "http" && Port == 80 ||
Scheme == "https" && Port == 443)
{
//https://stackoverflow.com/a/2819529/288936
return base.Uri.GetComponents(
UriComponents.AbsoluteUri & ~UriComponents.Port,
UriFormat.UriEscaped);
}
return base.ToString();
}
}
寫幾行程式實測,試了修改、刪除與增加 QueryString 參數,https 改 http,Port 變更等(Host 與 Path 也都可置換忘了示範),均得到預期的結果。
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
//Add parameter
var url = "http://blog.darkthread.net/";
var builder = new FlexUrlEditor(url);
builder["a"] = "1";
Console.WriteLine(builder.GenUrl());
//modify and remove parameter
url = "http://blog.darkthread.net/?a=1&b=2&c=3";
builder = new FlexUrlEditor(url);
builder["b"] = null; //set null to remove
builder["c"] = "中文"; //will be encoded
Console.WriteLine(builder.GenUrl());
//single command (C# 6.0+)
var newUrl = new FlexUrlEditor(
"https://blog.darkthread.net/?a=1&b=2&c=3")
{
["b"] = null,
["c"] = "中文"
};
Console.WriteLine(newUrl.GenUrl());
//read encoded query parameter
builder = new FlexUrlEditor(
"http://blog.darkthread.net/?a=1&b=%E4%B8%AD%E6%96%87");
Console.WriteLine($"a={builder["a"]}, b={builder["b"]}");
//change scheme & port
builder = new FlexUrlEditor("https://blog.darkthread.net");
builder.Scheme = "http";
builder.Port = 8888;
Console.WriteLine(builder.GenUrl());
Console.ReadLine();
}
}
}
執行結果如下:
工具箱再添順手工具一支~