6

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

5 Answers 5

18

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);
Sign up to request clarification or add additional context in comments.

1 Comment

but how to convert the model object to List<KeyValuePair>
10

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);
    }
}

2 Comments

Note: this won't work for objects with complex properties like objects or collections.
@Chartreugz agree with you. this is not intended for complex Objects or collections. However with a little bit of work, we can make it compatible with complex Objects
10

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.

1 Comment

Thanks. Will try out and let you know
0

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);

Comments

0

wanted to share my own solution based on apriestley answer (idealogically)

public static Dictionary<string, string> ToDictionary(this object obj)
{
    Dictionary<string, string> dictionary = new Dictionary<string, string>();
    PropertyInfo[] properties = obj.GetType().GetProperties();

    foreach (PropertyInfo property in properties)
    {
        var ignoreAttribute = property.GetCustomAttribute<JsonIgnoreAttribute>();
        if (ignoreAttribute != null)
            continue;

        string key = property.Name;
        JsonPropertyNameAttribute? attribute = property.GetCustomAttribute<JsonPropertyNameAttribute>();
        if (attribute != null)
        {
            key = attribute.Name;
        }

        string? value = property?.GetValue(obj)?.ToString();
        dictionary[key] = value;
    }

    return dictionary;
}

personally I wanted to get rid of Newtonsoft dependency. Unfortunatelly we are not able to use pure-reflection without handling the attributes.. so in the solution above I've put a logic to handle 2 attributes: JsonIgnore and JsonPropertyName.


public class TestClass
{
    [JsonPropertyName("aaa")]
    public string A { get; set; }

    [JsonIgnore]
    public string B { get; set; }

    public string C { get; set; }
}

will be converted to

"aaa": "",
"C": "",

where B is ignored entirely and A property is named based on attribute value.

Note: this won't work for objects with complex properties like objects or collections.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.