4

I am trying to use a home grown web API to retrieve some data. The documentation is all written in PHP. The example I'm looking at is this:

$params = array(
    'id' => 1
    ,'data' => array(
        ,'email' => '[email protected]'
    )

$url = "www.someapi.com/api?" . http_build_query( $params );

I'm using the C# WebClient class, but I can't figure out how to serialize the data parameter:

WebClient wc = new WebClient();
wc.QueryString["id"] = "1";
wc.QueryString["data"] = // I have no idea.

string json = wc.DownloadString(apiUrl);

I've tried a few variations:

wc.QueryString["data"] = "[email protected]";
wc.QueryString["data"] = Uri.EscapeDataString("data[email][email protected]");
wc.QueryString["data"] = Uri.EscapeDataString("email[0][email protected]");
wc.QueryString["data"] = Uri.EscapeDataString("[email protected]");

Of course, I don't have PHP setup anywhere to see what the http_build_query() is actually returning.

2 Answers 2

5

I finally figured it out. I guess posting this question rebooted my brain.

This is what worked:

wc.QueryString["data[email]"] = "[email protected]";
Sign up to request clarification or add additional context in comments.

2 Comments

Any idea how to solve this dynamicaly for array of arrays? Is there any C# equivalent of this PHP mess?
@vojta what do you mean arrays of arrays? What would you expect the keys to be?
2
+50

Note, this question is very relevant to this answer, so I put this class as an answer there as well. That answer will receive any updates to this class.

As far as I know, there's nothing built in to do this. But, you can create your own class.

So I did:

/// <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,
                   };
    }
  }
}

Like so:

// 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
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" },
         }
       }
  );

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.