2015-12-02 56 views
3

我需要通過我的C#客戶端使用HttpWebRequest將一些數據傳遞到服務器上的PHP頁面。根據文檔的預期數據是一組陣列,如下所示:C#相當於PHP http_build_query

$postData = array(
    'label1' => 'myLabel', 
    'label2' => array(
     'label2_1' => 3 
     'label2_2' => array(
      'label2_2_1' => 3 
     ) 
    ) 
); 

上面的結構僅僅是一個示例。它可能非常複雜,結構本身也不是恆定的。

在PHP中,有一個名爲http_build_query的函數,它將這些PHP嵌套數組序列化爲一個簡單的字符串,該字符串可以作爲HTTP POST請求的數據發送。問題是我需要從我的C#應用​​程序調用這個PHP頁面。我想將這些嵌套數組表示爲嵌套的Dictionary<string, object>或匿名類型。

我該怎麼做? http_build_query遵循什麼規則來產生其輸出字符串?

有一個非常類似的問題Converting PHP array of arrays to C#,這不能解決我的問題,不幸的是。接受的答案建議一個固定結構的解決方案,第二個完全不工作。

回答

11

嗯,似乎沒有任何東西內置到.NET,讓你做到這一點。但是,如果你想在.NET中重新實現PHP行爲,你可以通過查看PHP源代碼來實現它,或者通過讀取PHP documentation of http_build_query並在其上測試函數各種輸入。

我把一個黑箱方法,創造了以下類:

/// <summary> 
/// Helps up build a query string by converting an object into a set of named-values and making a 
/// query string out of it. 
/// </summary> 
public class QueryStringBuilder 
{ 
    private readonly List<KeyValuePair<string, object>> _keyValuePairs 
    = new List<KeyValuePair<string, object>>(); 

    /// <summary> Builds the query string from the given instance. </summary> 
    public static string BuildQueryString(object queryData, string argSeperator = "&") 
    { 
    var encoder = new QueryStringBuilder(); 
    encoder.AddEntry(null, queryData, allowObjects: true); 

    return encoder.GetUriString(argSeperator); 
    } 

    /// <summary> 
    /// Convert the key-value pairs that we've collected into an actual query string. 
    /// </summary> 
    private string GetUriString(string argSeperator) 
    { 
    return String.Join(argSeperator, 
         _keyValuePairs.Select(kvp => 
              { 
               var key = Uri.EscapeDataString(kvp.Key); 
               var value = Uri.EscapeDataString(kvp.Value.ToString()); 
               return $"{key}={value}"; 
              })); 
    } 

    /// <summary> Adds a single entry to the collection. </summary> 
    /// <param name="prefix"> The prefix to use when generating the key of the entry. Can be null. </param> 
    /// <param name="instance"> The instance to add. 
    /// 
    /// - If the instance is a dictionary, the entries determine the key and values. 
    /// - If the instance is a collection, the keys will be the index of the entries, and the value 
    /// will be each item in the collection. 
    /// - If allowObjects is true, then the object's properties' names will be the keys, and the 
    /// values of the properties will be the values. 
    /// - Otherwise the instance is added with the given prefix to the collection of items. </param> 
    /// <param name="allowObjects"> true to add the properties of the given instance (if the object is 
    /// not a collection or dictionary), false to add the object as a key-value pair. </param> 
    private void AddEntry(string prefix, object instance, bool allowObjects) 
    { 
    var dictionary = instance as IDictionary; 
    var collection = instance as ICollection; 

    if (dictionary != null) 
    { 
     Add(prefix, GetDictionaryAdapter(dictionary)); 
    } 
    else if (collection != null) 
    { 
     Add(prefix, GetArrayAdapter(collection)); 
    } 
    else if (allowObjects) 
    { 
     Add(prefix, GetObjectAdapter(instance)); 
    } 
    else 
    { 
     _keyValuePairs.Add(new KeyValuePair<string, object>(prefix, instance)); 
    } 
    } 

    /// <summary> Adds the given collection of entries. </summary> 
    private void Add(string prefix, IEnumerable<Entry> datas) 
    { 
    foreach (var item in datas) 
    { 
     var newPrefix = String.IsNullOrEmpty(prefix) 
     ? item.Key 
     : $"{prefix}[{item.Key}]"; 

     AddEntry(newPrefix, item.Value, allowObjects: false); 
    } 
    } 

    private struct Entry 
    { 
    public string Key; 
    public object Value; 
    } 

    /// <summary> 
    /// Returns a collection of entries that represent the properties on the object. 
    /// </summary> 
    private IEnumerable<Entry> GetObjectAdapter(object data) 
    { 
    var properties = data.GetType().GetProperties(); 

    foreach (var property in properties) 
    { 
     yield return new Entry() 
        { 
        Key = property.Name, 
        Value = property.GetValue(data) 
        }; 
    } 
    } 

    /// <summary> 
    /// Returns a collection of entries that represent items in the collection. 
    /// </summary> 
    private IEnumerable<Entry> GetArrayAdapter(ICollection collection) 
    { 
    int i = 0; 
    foreach (var item in collection) 
    { 
     yield return new Entry() 
        { 
        Key = i.ToString(), 
        Value = item, 
        }; 
     i++; 
    } 
    } 

    /// <summary> 
    /// Returns a collection of entries that represent items in the dictionary. 
    /// </summary> 
    private IEnumerable<Entry> GetDictionaryAdapter(IDictionary collection) 
    { 
    foreach (DictionaryEntry item in collection) 
    { 
     yield return new Entry() 
        { 
        Key = item.Key.ToString(), 
        Value = item.Value, 
        }; 
    } 
    } 
} 

的代碼是不言自明的,但它接受一個字典,數組或一個對象。如果它是頂級對象,則它將這些屬性序列化。如果它是一個數組,則每個元素都使用適當的數組索引進行序列化。如果它是字典,則鍵/值將被序列化。包含其他數組或字典的數組和字典值被展平,類似於PHP的行爲。

例如,以下內容:

QueryStringBuilder.BuildQueryString(new 
     { 
     Age = 19, 
     Name = "John&Doe", 
     Values = new object[] 
        { 
        1, 
        2, 
        new Dictionary<string, string>() 
        { 
         { "key1", "value1" }, 
         { "key2", "value2" }, 
        } 
        }, 
     }); 

// 0=1&1=2&2%5B0%5D=one&2%5B1%5D=two&2%5B2%5D=three&3%5Bkey1%5D=value1&3%5Bkey2%5D=value2 
QueryStringBuilder.BuildQueryString(new object[] 
     { 
     1, 
     2, 
     new object[] { "one", "two", "three" }, 
     new Dictionary<string, string>() 
     { 
      { "key1", "value1" }, 
      { "key2", "value2" }, 
     } 
     } 
); 

生成:

Age=19&Name=John%26Doe&Values%5B0%5D=1&Values%5B1%5D=2&Values%5B2%5D%5Bkey1%5D=value1&Values%5B2%5D%5Bkey2%5D=value2 

是:

Age=19&Name=John%26Doe&Values[0]=1&Values[1]=2&Values[2][key1]=value1&Values[2][key2]=value2 
Age=19 
Name=John&Doe 
Values[0]=1 
Values[1]=2 
Values[2][key1]=value1 
Values[2][key2]=value2 
+0

這是一顆寶石! @MackieChan – manishKungwani

+0

這確實是金。你可以用'if(allowObjects && instance.GetType()。IsClass && instance.GetType()。Name!=「String」)編輯並在'Add'函數內設置'allowObjects'爲'true',這樣你就可以擁有與對象的字典。 –

-1

使用的NameValueCollection你可以這樣做:

private string ToQueryString(NameValueCollection queryData) 
{ 
    var array = (from key in queryData.AllKeys 
     from value in queryData.GetValues(key) 
     select string.Format(CultureInfo.InvariantCulture, "{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))) 
     .ToArray(); 
    return "?" + string.Join("&", array); 
} 
+0

我怎麼能窩'NameValueCollection'一個'NameValueCollection'裏面?恐怕這不起作用。 – vojta