Building on @GWB's suggestion of using a RouteValueDictionary
, I wrote this recursive function to support nested anonymous types, prefixing those nested parameters by their parents' keys.
public static string EncodeHtmlRequestBody(object data, string parent = null) {
var keyValuePairs = new List<string>();
var dict = new RouteValueDictionary(data);
foreach (var pair in dict) {
string key = parent == null ? pair.Key : parent + "." + pair.Key;
var type = pair.Value.GetType();
if (type.IsPrimitive || type == typeof(decimal) || type == typeof(string)) {
keyValuePairs.Add(key + "=" + Uri.EscapeDataString((string)pair.Value).Replace("%20", "+"));
} else {
keyValuePairs.Add(EncodeHtmlRequestBody(pair.Value, key));
}
}
return String.Join("&", keyValuePairs);
}
Example usage:
var data = new {
apiOperation = "AUTHORIZE",
order = new {
id = "order123",
amount = "101.00",
currency = "AUD"
},
transaction = new {
id = "transaction123"
},
sourceOfFunds = new {
type = "CARD",
provided = new {
card = new {
expiry = new {
month = "1",
year = "20"
},
nameOnCard = "John Smith",
number = "4444333322221111",
securityCode = "123"
}
}
}
};
string encodedData = EncodeHtmlRequestBody(data);
encodedData
becomes:
"apiOperation=AUTHORIZE&order.id=order123&order.amount=101.00&order.currency=AUD&transaction.id=transaction123&sourceOfFunds.type=CARD&sourceOfFunds.provided.card.expiry.month=1&sourceOfFunds.provided.card.expiry.year=20&sourceOfFunds.provided.card.nameOnCard=John+Smith&sourceOfFunds.provided.card.number=4444333322221111&sourceOfFunds.provided.card.securityCode=123"
Hope this helps someone else in a similar situation.
Edit: As DrewG pointed out, this doesn't support arrays. To properly implement support for arbitrarily nested arrays with anonymous types would be non-trivial, and as none of the APIs I've used have accepted arrays either (I'm not sure there's even a standardised way of serialising them with form encoding), I'll leave that to you folks if you need to support them.