100

How do I serialize an object into query-string format? I can't seem to find an answer on google. Thanks.

Here is the object I will serialize as an example.

public class EditListItemActionModel
{
    public int? Id { get; set; }
    public int State { get; set; }
    public string Prefix { get; set; }
    public string Index { get; set; }
    public int? ParentID { get; set; }
}
Benjamin
  • 3,134
  • 6
  • 36
  • 57
  • Why not create your own function to serialize this way? – James Black Jul 27 '11 at 17:05
  • You want to end up with: Id=1&State=CA&Prefix=Mr... something like that? If so, I'm agreeing with @James. – Bob Kaufman Jul 27 '11 at 17:09
  • 3
    @James Wow, is that the only way? I figured there was something built into .NET somewhere. I'm thinking kind of like the inverse of the MVC model binder. There must be a method for this right? – Benjamin Jul 27 '11 at 17:09
  • If there is no built in function, can you give me a clue how to write one? – Benjamin Jul 27 '11 at 17:12
  • Are you using ASP.NET MVC or plain ASP.NET? – NOtherDev Jul 27 '11 at 17:15
  • @A. Can't the necessary namespace be imported either way? – Benjamin Jul 27 '11 at 17:21
  • 3
    [Flurl](http://tmenier.github.io/Flurl/) is a URL builder/HTTP client that uses objects extensively for name-value-pair-like things (query strings, headers, URL-encoded form values, etc). [`SetQueryParams`](http://tmenier.github.io/Flurl/fluent-url/) does exactly what you're looking for. If you just want the URL builder and not all the HTTP stuff, it's available [here](https://www.nuget.org/packages/Flurl/). [disclaimer: I'm the author] – Todd Menier Oct 13 '15 at 13:29
  • @ToddMenier Nice library, but how can I use it for camel cased serialization? – Pavel Biryukov Mar 23 '17 at 07:44
  • @PavelBiryukov You want TitleCase object properties serialized to camelCase query string values? Flurl can't do that, it takes the property names as-is. – Todd Menier Mar 25 '17 at 17:07
  • @ToddMenier That would be a nice option :) Like in asp.net core (web api) you can configure serialization to json as camelCase (default, which is common for javascript) or TitleCase. I've made a hepler method using Microsoft.AspNetCore.WebUtilities.QueryHelpers at last... – Pavel Biryukov Mar 27 '17 at 07:50
  • @PavelBiryukov Are you talking about JSON in the body or JSON in the query string? For the body, Flurl's default serializer is Json.NET and you can easily hook into its settings for camel case support. See [this issue](https://github.com/tmenier/Flurl/issues/49) (toward the bottom). If you're talking about JSON in the query string, that's not a very common thing. – Todd Menier Mar 27 '17 at 19:23
  • @ToddMenier I mean LIKE in asp.net core... – Pavel Biryukov Mar 28 '17 at 19:52
  • This is the other solution that you may want to take a look https://ole.michelsen.dk/blog/serialize-object-into-a-query-string-with-reflection/ – Duy Hoang Nov 14 '21 at 14:11

14 Answers14

142

I'm 99% sure there's no built-in utility method for this. It's not a very common task, since a web server doesn't typically respond with a URLEncoded key/value string.

How do you feel about mixing reflection and LINQ? This works:

var foo = new EditListItemActionModel() {
  Id = 1,
  State = 26,
  Prefix = "f",
  Index = "oo",
  ParentID = null
};

var properties = from p in foo.GetType().GetProperties()
                 where p.GetValue(foo, null) != null
                 select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(foo, null).ToString());

// queryString will be set to "Id=1&State=26&Prefix=f&Index=oo"                  
string queryString = String.Join("&", properties.ToArray());

Update:

To write a method that returns the QueryString representation of any 1-deep object, you could do this:

public string GetQueryString(object obj) {
  var properties = from p in obj.GetType().GetProperties()
                   where p.GetValue(obj, null) != null
                   select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString());

  return String.Join("&", properties.ToArray());
}

// Usage:
string queryString = GetQueryString(foo);

You could also make it an extension method without much additional work

public static class ExtensionMethods {
  public static string GetQueryString(this object obj) {
    var properties = from p in obj.GetType().GetProperties()
                     where p.GetValue(obj, null) != null
                     select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString());

    return String.Join("&", properties.ToArray());
  }
}

// Usage:
string queryString = foo.GetQueryString();
Dave Ward
  • 59,815
  • 13
  • 117
  • 134
  • This is nice. I'm trying to make it a function that takes a dynamic parameter but I assume I am messing up the dynamic linq select syntax. `public string SerializeWithDynamicLINQ(dynamic Thing) { var Properties = Thing.GetType().GetProperties().ToArray(); return "&" + Properties.select("Property.Name") + "=" + HttpUtility.UrlEncode( Properties.select("Property").GetValue(Thing, null).ToString()); }` I can't figure out how to do code blocks in comments either.. – Benjamin Jul 27 '11 at 19:32
  • @Benjamin: Updated my answer to help with that. – Dave Ward Jul 27 '11 at 20:15
  • 1
    Since you say it's not a very common task. What is the alternative approach to pass in lots of form values, without having to hard code all the RouteValueDictionary values – Doug Chamberlain Apr 16 '13 at 14:52
  • 2
    You can make this more efficient by getting the value of each property only once by assigning it to a temporary variable, e.g. using `let value = p.GetValue(obj, null)`. – WhatIsHeDoing Aug 03 '15 at 12:38
  • 2
    This works only for English Culture Info on server. If you set specific datetime format on Windows, this will not work, because you should set CultureInfo Invariant as ToString parameter. The same problem is with specific decimal separator in float/double. – Tomas Kubes Dec 21 '18 at 15:17
25

Using Json.Net it would be much easier, by serializing and then deserializing to key value pairs.

Here is a code example:

using Newtonsoft.Json;
using System.Web;

string ObjToQueryString(object obj)
{
     var step1 = JsonConvert.SerializeObject(obj);

     var step2 = JsonConvert.DeserializeObject<IDictionary<string, string>>(step1);

     var step3 = step2.Select(x => HttpUtility.UrlEncode(x.Key) + "=" + HttpUtility.UrlEncode(x.Value));

     return string.Join("&", step3);
}
yoel halb
  • 12,188
  • 3
  • 57
  • 52
21

Building on the good ideas from other comments, I have made a generic extension method .ToQueryString(), which can be used on any object.

public static class UrlHelpers
{
    public static string ToQueryString(this object request, string separator = ",")
    {
        if (request == null)
            throw new ArgumentNullException("request");

        // Get all properties on the object
        var properties = request.GetType().GetProperties()
            .Where(x => x.CanRead)
            .Where(x => x.GetValue(request, null) != null)
            .ToDictionary(x => x.Name, x => x.GetValue(request, null));

        // Get names for all IEnumerable properties (excl. string)
        var propertyNames = properties
            .Where(x => !(x.Value is string) && x.Value is IEnumerable)
            .Select(x => x.Key)
            .ToList();

        // Concat all IEnumerable properties into a comma separated string
        foreach (var key in propertyNames)
        {
            var valueType = properties[key].GetType();
            var valueElemType = valueType.IsGenericType
                                    ? valueType.GetGenericArguments()[0]
                                    : valueType.GetElementType();
            if (valueElemType.IsPrimitive || valueElemType == typeof (string))
            {
                var enumerable = properties[key] as IEnumerable;
                properties[key] = string.Join(separator, enumerable.Cast<object>());
            }
        }

        // Concat all key/value pairs into a string separated by ampersand
        return string.Join("&", properties
            .Select(x => string.Concat(
                Uri.EscapeDataString(x.Key), "=",
                Uri.EscapeDataString(x.Value.ToString()))));
    }
}

It will also work for objects that have properties of the type Array and generic Lists if they only contain primitives or strings.

Try it out, comments are welcome: Serialize object into a query string with Reflection

johnnyRose
  • 7,310
  • 17
  • 40
  • 61
Ole
  • 269
  • 1
  • 3
  • 8
13

Based on the the popular answers, I needed to update the code to support arrays as well. Sharing the implementation:

public string GetQueryString(object obj)
{
    var result = new List<string>();
    var props = obj.GetType().GetProperties().Where(p => p.GetValue(obj, null) != null);
    foreach (var p in props)
    {
        var value = p.GetValue(obj, null);
        var enumerable = value as ICollection;
        if (enumerable != null)
        {
            result.AddRange(from object v in enumerable select string.Format("{0}={1}", p.Name, HttpUtility.UrlEncode(v.ToString())));
        }
        else
        {
            result.Add(string.Format("{0}={1}", p.Name, HttpUtility.UrlEncode(value.ToString())));
        }
    }

    return string.Join("&", result.ToArray());
}
Alvis
  • 3,543
  • 5
  • 36
  • 41
5

It will also be useful for nested objects

public static class HttpQueryStrings
{
    private static readonly StringBuilder _query = new();

    public static string ToQueryString<T>(this T @this) where T : class
    {
        _query.Clear();

        BuildQueryString(@this, "");

        if (_query.Length > 0) _query[0] = '?';

        return _query.ToString();
    }

    private static void BuildQueryString<T>(T? obj, string prefix = "") where T : class
    {
        if (obj == null) return;

        foreach (var p in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (p.GetValue(obj, Array.Empty<object>()) != null)
            {
                var value = p.GetValue(obj, Array.Empty<object>());


                if (p.PropertyType.IsArray && value?.GetType() == typeof(DateTime[]))
                    foreach (var item in (DateTime[])value)
                        _query.Append($"&{prefix}{p.Name}={item.ToString("yyyy-MM-dd")}");

                else if (p.PropertyType.IsArray)
                    foreach (var item in (Array)value!)
                        _query.Append($"&{prefix}{p.Name}={item}");

                else if (p.PropertyType == typeof(string))
                    _query.Append($"&{prefix}{p.Name}={value}");

                else if (p.PropertyType == typeof(DateTime) && !value!.Equals(Activator.CreateInstance(p.PropertyType))) // is not default 
                    _query.Append($"&{prefix}{p.Name}={((DateTime)value).ToString("yyyy-MM-dd")}");

                else if (p.PropertyType.IsValueType && !value!.Equals(Activator.CreateInstance(p.PropertyType))) // is not default 
                    _query.Append($"&{prefix}{p.Name}={value}");


                else if (p.PropertyType.IsClass)
                    BuildQueryString(value, $"{prefix}{p.Name}.");
            }
        }
    }
}

An example of using the solution:

string queryString = new
{
    date = new DateTime(2020, 1, 1),
    myClass = new MyClass
    {
        FirstName = "john",
        LastName = "doe"
    },
    myArray = new int[] { 1, 2, 3, 4 },
}.ToQueryString();
giorgi02
  • 683
  • 6
  • 13
4

Perhaps this Generic approach will be useful:

    public static string ConvertToQueryString<T>(T entity) where T: class
    {
        var props = typeof(T).GetProperties();

        return $"?{string.Join('&', props.Where(r=> r.GetValue(entity) != null).Select(r => $"{HttpUtility.UrlEncode(r.Name)}={HttpUtility.UrlEncode(r.GetValue(entity).ToString())}"))}";
    }
Lug
  • 420
  • 5
  • 15
3
public static class UrlHelper
{
    public static string ToUrl(this Object instance)
    {
        var urlBuilder = new StringBuilder();
        var properties = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
        for (int i = 0; i < properties.Length; i++)
        {
            urlBuilder.AppendFormat("{0}={1}&", properties[i].Name, properties[i].GetValue(instance, null));
        }
        if (urlBuilder.Length > 1)
        {
            urlBuilder.Remove(urlBuilder.Length - 1, 1);
        }
        return urlBuilder.ToString();
    }
}
Alexander
  • 1,152
  • 1
  • 16
  • 18
3

This my solution:

public static class ObjectExtensions
{
    public static string ToQueryString(this object obj)
    {
        if (!obj.GetType().IsComplex())
        {
            return obj.ToString();
        }

        var values = obj
            .GetType()
            .GetProperties()
            .Where(o => o.GetValue(obj, null) != null);

        var result = new QueryString();

        foreach (var value in values)
        {
            if (!typeof(string).IsAssignableFrom(value.PropertyType) 
                && typeof(IEnumerable).IsAssignableFrom(value.PropertyType))
            {
                var items = value.GetValue(obj) as IList;
                if (items.Count > 0)
                {
                    for (int i = 0; i < items.Count; i++)
                    {
                        result = result.Add(value.Name, ToQueryString(items[i]));
                    }
                }
            }
            else if (value.PropertyType.IsComplex())
            {
                result = result.Add(value.Name, ToQueryString(value));
            }
            else
            {
                result = result.Add(value.Name, value.GetValue(obj).ToString());
            }
        }

        return result.Value;
    }

    private static bool IsComplex(this Type type)
    {
        var typeInfo = type.GetTypeInfo();
        if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            // nullable type, check if the nested type is simple.
            return IsComplex(typeInfo.GetGenericArguments()[0]);
        }
        return !(typeInfo.IsPrimitive
          || typeInfo.IsEnum
          || type.Equals(typeof(Guid))
          || type.Equals(typeof(string))
          || type.Equals(typeof(decimal)));
    }
}

I use this extension for my integration test, it works perfectly :)

Cedric Arnould
  • 1,991
  • 4
  • 29
  • 49
2

Just another variation of the above, but I wanted to utilize the existing DataMember attributes in my model class, so only the properties I want to serialize are sent to the server in the url in the GET request.

    public string ToQueryString(object obj)
    {
        if (obj == null) return "";

        return "?" + string.Join("&", obj.GetType()
                                   .GetProperties()
                                   .Where(p => Attribute.IsDefined(p, typeof(DataMemberAttribute)) && p.GetValue(obj, null) != null)
                                   .Select(p => $"{p.Name}={Uri.EscapeDataString(p.GetValue(obj).ToString())}"));
    }
Peter Kerr
  • 1,649
  • 22
  • 33
1

Here is something I wrote that does what you need.

    public string CreateAsQueryString(PageVariables pv) //Pass in your EditListItemActionModel instead
    {
        int i = 0;
        StringBuilder sb = new StringBuilder();

        foreach (var prop in typeof(PageVariables).GetProperties())
        {
            if (i != 0)
            {
                sb.Append("&");
            }

            var x = prop.GetValue(pv, null).ToString();

            if (x != null)
            {
                sb.Append(prop.Name);
                sb.Append("=");
                sb.Append(x.ToString());
            }

            i++;
        }

        Formating encoding = new Formating();
        // I am encoding my query string - but you don''t have to
        return "?" + HttpUtility.UrlEncode(encoding.RC2Encrypt(sb.ToString()));  
    }
TheGeekYouNeed
  • 7,509
  • 2
  • 26
  • 43
0

I was looking for a solution to this for a Windows 10 (UWP) App. Taking the Relection approach suggested by Dave, and after adding the Microsoft.AspNet.WebApi.Client Nuget package, I used the following code, which handles Url Encoding of the property values:

 private void AddContentAsQueryString(ref Uri uri, object content)
    {            
        if ((uri != null) && (content != null))
        {
            UriBuilder builder = new UriBuilder(uri);

            HttpValueCollection query = uri.ParseQueryString();

            IEnumerable<PropertyInfo> propInfos = content.GetType().GetRuntimeProperties();

            foreach (var propInfo in propInfos)
            {
                object value = propInfo.GetValue(content, null);
                query.Add(propInfo.Name, String.Format("{0}", value));
            }

            builder.Query = query.ToString();
            uri = builder.Uri;                
        }
    }
Community
  • 1
  • 1
Howard
  • 694
  • 5
  • 21
0

A simple approach that supports list properties:

public static class UriBuilderExtensions
{
    public static UriBuilder SetQuery<T>(this UriBuilder builder, T parameters)
    {
        var fragments = typeof(T).GetProperties()
            .Where(property => property.CanRead)
            .Select(property => new
            {
                property.Name,
                Value = property.GetMethod.Invoke(parameters, null)
            })
            .Select(pair => new
            {
                pair.Name,
                List = (!(pair.Value is string) && pair.Value is IEnumerable list ? list.Cast<object>() : new[] { pair.Value })
                    .Select(element => element?.ToString())
                    .Where(element => !string.IsNullOrEmpty(element))
            })
            .Where(pair => pair.List.Any())
            .SelectMany(pair => pair.List.Select(value => Uri.EscapeDataString(pair.Name) + '=' + Uri.EscapeDataString(value)));

        builder.Query = string.Join("&", fragments);
        return builder;
    }
}

A faster solution which is as fast as spelling out the code to serialize each type:

public static class UriBuilderExtensions
{
    public static UriBuilder SetQuery<TSource>(this UriBuilder builder, TSource parameters)
    {
        var fragments = Cache<TSource>.Properties
            .Select(property => new
            {
                property.Name,
                List = property.FetchValue(parameters)?.Where(item => !string.IsNullOrEmpty(item))
            })
            .Where(parameter => parameter.List?.Any() ?? false)
            .SelectMany(pair => pair.List.Select(item => Uri.EscapeDataString(pair.Name) + '=' + Uri.EscapeDataString(item)));

        builder.Query = string.Join("&", fragments);
        return builder;
    }

    /// <summary>
    /// Caches dynamically emitted code which converts a types getter property values to a list of strings.
    /// </summary>
    /// <typeparam name="TSource">The type of the object being serialized</typeparam>
    private static class Cache<TSource>
    {
        public static readonly IEnumerable<IProperty> Properties =
            typeof(TSource).GetProperties()
            .Where(propertyInfo => propertyInfo.CanRead)
            .Select(propertyInfo =>
            {
                var source = Expression.Parameter(typeof(TSource));
                var getter = Expression.Property(source, propertyInfo);
                var cast = Expression.Convert(getter, typeof(object));
                var expression = Expression.Lambda<Func<TSource, object>>(cast, source).Compile();
                return new Property
                {
                    Name = propertyInfo.Name,
                    FetchValue = typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType) && propertyInfo.PropertyType != typeof(string) ?
                        CreateListFetcher(expression) :
                        CreateValueFetcher(expression)
                };
            })
            .OrderBy(propery => propery.Name)
            .ToArray();

        /// <summary>
        /// Creates a function which serializes a <see cref="IEnumerable"/> property value to a list of strings.
        /// </summary>
        /// <param name="get">A lambda function which retrieves the property value from a given source object.</param>
        private static Func<TSource, IEnumerable<string>> CreateListFetcher(Func<TSource, object> get)
           => obj => ((IEnumerable)get(obj))?.Cast<object>().Select(item => item?.ToString());

        /// <summary>
        /// Creates a function which serializes a <see cref="object"/> property value to a list of strings.
        /// </summary>
        /// <param name="get">A lambda function which retrieves the property value from a given source object.</param>
        private static Func<TSource, IEnumerable<string>> CreateValueFetcher(Func<TSource, object> get)
            => obj => new[] { get(obj)?.ToString() };

        public interface IProperty
        {
            string Name { get; }
            Func<TSource, IEnumerable<string>> FetchValue { get; }
        }

        private class Property : IProperty
        {
            public string Name { get; set; }
            public Func<TSource, IEnumerable<string>> FetchValue { get; set; }
        }
    }
}

An example of using either solution:

var url = new UriBuilder("test.com").SetQuerySlow(new
{
    Days = new[] { WeekDay.Tuesday, WeekDay.Wednesday },
    Time = TimeSpan.FromHours(14.5),
    Link = "conferences.com/apple/stream/15",
    Pizzas = default(int?)
}).Uri;

Output:
http://test.com/Days=Tuesday&Days=Wednesday&Time=14:30:00&Link=conferences.com%2Fapple%2Fstream%2F15
Neither of the solutions handle exotic types, indexed parameters, or nested parameters.

When manual serialization is simpler, this c#7/.net4.7 approach can help:

public static class QueryParameterExtensions
{
    public static UriBuilder SetQuery(this UriBuilder builder, params (string Name, object Obj)[] parameters)
    {
        var list = parameters
            .Select(parameter => new
            {
                parameter.Name,
                Values = SerializeToList(parameter.Obj).Where(value => !string.IsNullOrEmpty(value))
            })
            .Where(parameter => parameter.Values.Any())
            .SelectMany(parameter => parameter.Values.Select(item => Uri.EscapeDataString(parameter.Name) + '=' + Uri.EscapeDataString(item)));
        builder.Query = string.Join("&", list);
        return builder;
    }

    private static IEnumerable<string> SerializeToList(object obj)
    {
        switch (obj)
        {
            case string text:
                yield return text;
                break;
            case IEnumerable list:
                foreach (var item in list)
                {
                    yield return SerializeToValue(item);
                }
                break;
            default:
                yield return SerializeToValue(obj);
                break;
        }
    }

    private static string SerializeToValue(object obj)
    {
        switch (obj)
        {
            case bool flag:
                return flag ? "true" : null;
            case byte number:
                return number == default(byte) ? null : number.ToString();
            case short number:
                return number == default(short) ? null : number.ToString();
            case ushort number:
                return number == default(ushort) ? null : number.ToString();
            case int number:
                return number == default(int) ? null : number.ToString();
            case uint number:
                return number == default(uint) ? null : number.ToString();
            case long number:
                return number == default(long) ? null : number.ToString();
            case ulong number:
                return number == default(ulong) ? null : number.ToString();
            case float number:
                return number == default(float) ? null : number.ToString();
            case double number:
                return number == default(double) ? null : number.ToString();
            case DateTime date:
                return date == default(DateTime) ? null : date.ToString("s");
            case TimeSpan span:
                return span == default(TimeSpan) ? null : span.ToString();
            case Guid guid:
                return guid == default(Guid) ? null : guid.ToString();
            default:
                return obj?.ToString();
        }
    }
}

Example usage:

var uri = new UriBuilder("test.com")
    .SetQuery(("days", standup.Days), ("time", standup.Time), ("link", standup.Link), ("pizzas", standup.Pizzas))
    .Uri;

Output:
http://test.com/?days=Tuesday&days=Wednesday&time=14:30:00&link=conferences.com%2Fapple%2Fstream%2F15

Rjz
  • 61
  • 1
  • 2
0

In addition to existing answers

public static string ToQueryString<T>(this T input)
        {
            if (input == null)
            {
                return string.Empty;
            }
                
            var queryStringBuilder = new StringBuilder("?");

            var properties = input.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

            foreach (var property in properties)
            {
                var value = property.GetValue(input);
                if (value is null || property.HasIgnoreDataMember())
                    continue;

                queryStringBuilder.AppendFormat("{0}={1}&", property.GetName(), HttpUtility.UrlEncode(value.ToString()));
            }
            queryStringBuilder.Length--;

            return queryStringBuilder.ToString();
        }

        private static bool HasIgnoreDataMember(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetCustomAttribute(typeof(IgnoreDataMemberAttribute), true) is not null;
        }

        private static DataMemberAttribute GetDataMemberAttribute(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetCustomAttribute<DataMemberAttribute>();
        }

        private static T GetCustomAttribute<T>(this PropertyInfo propertyInfo) where T : class
        {
            return propertyInfo.GetCustomAttribute(typeof(T), true) as T;
        }

        private static string GetName(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetDataMemberAttribute()?.Name ?? propertyInfo.Name;
        }
    }

Usage: var queryString = object.ToQueryString()

Maruf
  • 354
  • 3
  • 8
-3

Faced with a similar situation what I did, is to XML serialize the object and pass it around as query string parameter. The difficulty with this approach was that despite encoding, the receiving form throws exception saying "potentially dangerous request...". The way I got around was to encrypt the serialized object and then encode to pass it around as query string parameter. Which in turn made the query string tamper proof (bonus wandering into the HMAC territory)!

FormA XML serializes an object > encrypts the serialized string > encode > pass as query string to FormB FormB decrypts the query parameter value (as request.querystring decodes also) > deserialize the resulting XML string to object using XmlSerializer.

I can share my VB.NET code upon request to howIdidit-at-applecart-dot-net