專案有個 HTTP POST 呼叫特定 URL 的共用函式需求,參數採完全彈性,給什麼就組什麼,直接轉成 p1=...&p2=...&p3=... application/x-www-form-urlencode 格式送出。

若用 .NET 寫,最省事的做法是將參數轉成 NameValueCollection 傳給 WebClient.UploadValues(),全靠現成元件,輕鬆秒殺。(延伸閱讀:利用 WebClient 類別模擬 HTTP POST 表單送出的注意事項 by 保哥)

對呼叫端而言,用 Dictionary<string, string> 比 NameValueCollection 順手,因此我的第一個版本長這樣:

static string url = "http://localhost/aspnet/post/";
static void ClassicPostRequest(Dictionary<string, string> data)
{
    var wc = new WebClient();
    var nvc = new NameValueCollection();
    foreach (var kv in data)
        nvc[kv.Key] = kv.Value;
    var resp = wc.UploadValues(url, nvc);
    Console.WriteLine(Encoding.UTF8.GetString(resp));
}
static void Main(string[] args)
{
    var guid = Guid.NewGuid();
    var time = DateTime.Now.ToString("yyyy-MM-dd");

    ClassicPostRequest(new Dictionary<string, string>
    {
        ["num"] = "123",
        ["letters"] = "ABC",
        ["time"] = time,
        ["guid"] = guid.ToString()
    });
    Console.Read();
}

如果別太挑剔,這個版本已經蠻好用。

BUT! Dapper 寫多用慣 new { prop1, prop2, prop3 = ... } 傳參數的簡潔寫法,new Dictionary<string, string> { ["prop1"] = prop1Value ... } 怎麼看怎麼 Low,大概就是所謂「曾經滄海難為水」吧。

cn.Query<ResultItem>("SELECT * FROM Items WHERE Col1 = @p1 AND Col2 = @p2 AND Col3 IN @array", 
    new { p1, p2, array = new string[] { "A", "B" } });

何不仿效 Dapper,也用自訂匿名物件彈性指定參數呢?試寫了一下,System.Reflection 加 LINQ 三兩下搞定:

static string url = "http://localhost/aspnet/post/";

static void NewPostRequest(object args) {
    var wc = new WebClient();
    var resp = wc.UploadValues(url, AnonymousTypeToNameValueCollection(args));
    Console.WriteLine(Encoding.UTF8.GetString(resp));
}

static NameValueCollection AnonymousTypeToNameValueCollection(object args)
{
    var postData = new NameValueCollection();
    args.GetType().GetProperties()
    .ToList()
    .ForEach(p =>
    {
        var valObj = p.GetValue(args);
        //TODO 視需要自訂物件轉字串邏輯
        var valStr = p.PropertyType == typeof(DateTime) ?
            ((DateTime)valObj).ToString("yyyy-MM-dd HH:mm:ss") :
            valObj.ToString();
        postData.Add(p.Name, valStr);
    });
    return postData;
}

static void Main(string[] args)
{
    var guid = Guid.NewGuid();
    var time = DateTime.Now.ToString("yyyy-MM-dd");

    NewPostRequest(new 
    {
        num = "123",
        letters = "ABC",
        time,
        guid
    });
    Console.Read();
}

是不是優雅多了?

Example of passing parameters as anonymous object to function in C#


Comments

# by Ammon

有個 RouteValueDictionary 可以參考看看 https://docs.microsoft.com/zh-tw/dotnet/api/system.web.routing.routevaluedictionary.-ctor?view=netframework-4.8#System_Web_Routing_RouteValueDictionary__ctor_System_Object_

# by 凱大

static void NewPostRequest<T>(T args, Func<T, NameValueCollection> func) { var wc = new WebClient(); var resp = wc.UploadValues(url, func(args)); Console.WriteLine(Encoding.UTF8.GetString(resp)); } NewPostRequest(new { num = "123", letters = "ABC", time, guid }, obj => /* 在這裡 obj 的型別為前面的匿名型別 可以不用 reflection 就存取 property */ );

# by ByTIM

用 dynamic ,不知道行不行?

Post a comment