c# Build URL encoded query from model object for HttpClient

Viewed 12711

Need to build URL encoded query from model object for HttpClient

My Model is

class SaveProfileRequest
{
    public string gName { get; set; }
    public string gEmail { get; set; }
    public long gContact { get; set; }
    public string gCompany { get; set; }
    public string gDeviceID { get; set; }
    public string Organization { get; set; }
    public string profileImage { get; set; }
    public string documentImagefront { get; set; }
    public string documentImageback { get; set; }
}

SaveProfileRequest request = new SaveProfileRequest() { gName = name, gEmail = email, gContact = phonenumber, gCompany = company,
            gDeviceID = deviceId, Organization = "", profileImage = "", documentImageback = "", documentImagefront = "" };

string response = await RequestProvider.PostAsync<string, SaveProfileRequest>(uri, request);

I have a working code for content type JSON

content = new StringContent(JsonConvert.SerializeObject(data));

content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

Where data is of type TInput

Tried

content = new StringContent(System.Net.WebUtility.UrlEncode(JsonConvert.SerializeObject(data)));

content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");

but didn't workout

4 Answers

JsonConvert produces only json content. For urlencoded query you should construct instance of FormUrlEncodedContent. As constructor parameter it takes collection of KeyValuePair<string, string>. So the main trick is to build this collection for model object.

You could use reflection for this purpose. But there is a simpler solution based on Json.net. It was described here and following ToKeyValue() extension method is a copy/paste from that blog post:

public static class ObjectExtensions
{
    public static IDictionary<string, string> ToKeyValue(this object metaToken)
    {
        if (metaToken == null)
        {
            return null;
        }

        JToken token = metaToken as JToken;
        if (token == null)
        {
            return ToKeyValue(JObject.FromObject(metaToken));
        }

        if (token.HasValues)
        {
            var contentData = new Dictionary<string, string>();
            foreach (var child in token.Children().ToList())
            {
                var childContent = child.ToKeyValue();
                if (childContent != null)
                {
                    contentData = contentData.Concat(childContent)
                        .ToDictionary(k => k.Key, v => v.Value);
                }
            }

            return contentData;
        }

        var jValue = token as JValue;
        if (jValue?.Value == null)
        {
            return null;
        }

        var value = jValue?.Type == JTokenType.Date ?
            jValue?.ToString("o", CultureInfo.InvariantCulture) :
            jValue?.ToString(CultureInfo.InvariantCulture);

        return new Dictionary<string, string> { { token.Path, value } };
    }
}

Now you could build the url-encoded content as easy as:

var keyValues = data.ToKeyValue();
var content = new FormUrlEncodedContent(keyValues);

This can be done effortlessly with Flurl (disclaimer: I'm the author) as this exact scenario - parsing a .NET object's properties into URL-encoded key-value pairs - is supported right out the box:

using Flurl.Http;

var resp = await url.PostUrlEncodedAsync(data);

Flurl uses HttpClient under the hood, so in the example above, resp is an instance of HttpResponseMessage, just as if the call was made using HttpClient directly. You could also append .ReceiveString() to the call if you just want the response body, or .ReceiveJson<T>() if you're expecting a JSON response and want a matching .NET object of type T.

You can use this simplified version

public static class URLExtensions
{
    public static string ToKeyValueURL(this object obj)
    {
        var keyvalues = obj.GetType().GetProperties()
            .ToList()
            .Select(p => $"{p.Name} = {p.GetValue(obj)}")
            .ToArray();

        return string.Join('&',keyvalues);
    }
}

If I may expand upon CodeFuller's answer, I wanted to create a couple of convenience methods in the same vein as System.Net.Http.Json's extensions for HttpClient.

Much like you can do PostAsJsonAsync() / PutAsJsonAsync(), I've created this simple extensions class to add to the one previously posted:

public static class HttpClientExtensions
{
    public static Task<HttpResponseMessage> PostAsFormUrlEncodedAsync<T>(this HttpClient httpClient, string? requestUri, T? content)
    {
        var dict = content?.ToKeyValue();
        var formContent = dict is not null ? new FormUrlEncodedContent(dict) : null;
        return httpClient.PostAsync(requestUri, formContent);
    }

    public static Task<HttpResponseMessage> PutAsFormUrlEncodedAsync<T>(this HttpClient httpClient, string? requestUri, T? content)
    {
        var dict = content?.ToKeyValue();
        var formContent = dict is not null ? new FormUrlEncodedContent(dict) : null;
        return httpClient.PutAsync(requestUri, formContent);
    }
}

You can use it like the aforementioned HttpClient methods:

var response = await httpClient.PostAsUrlEncodedAsync("api/customer", customer);
Related