43

I have this (simplified) class:

public class StarBuildParams
{
    public int BaseNo { get; set; }
    public int Width { get; set; }
}

And I have to transform instances of it to a querystring like this:

"BaseNo=5&Width=100"

Additionally I have to transform such a querystring back in an object of that class.

I know that this is pretty much what a modelbinder does, but I don't have the controller context in my situation (some deep buried class running in a thread).

So, is there a simple way to convert a object in a query string and back without having a controller context?

It would be great to use the modelbinding but I don't know how.

Marc
  • 6,749
  • 9
  • 47
  • 78
  • All that I can think of is using reflection. – ojlovecd Mar 22 '12 at 06:46
  • @ojlovecd How would reflection help here? – Kirk Broadhurst Mar 22 '12 at 06:53
  • I am sure that this situation can be avoided. Could you describe your scenario? How did you end up with a query string in this class? – Darin Dimitrov Mar 22 '12 at 06:56
  • Hi Darin, I was waiting for your response because if you say it's not possible I believe it :) I try to store some information about manipulating an image and need that as a querystring because I use the [ImageResizer](http://imageresizing.net/) and I have to pass a a query string there. Additionally I want to store some own data in it. And my imagegenerator runs in a thread over a couple of images each with its own manipulation info. – Marc Mar 22 '12 at 07:06
  • Wow, 1000 views and no upvote... – Marc Jun 07 '13 at 00:59
  • It may work https://stackoverflow.com/a/60936159/9590885 – giorgi02 Mar 30 '20 at 17:50

8 Answers8

43

A solution with Newtonsoft Json serializer and linq:

string responseString = "BaseNo=5&Width=100";
var dict = HttpUtility.ParseQueryString(responseString);
string json = JsonConvert.SerializeObject(dict.Cast<string>().ToDictionary(k => k, v => dict[v]));
StarBuildParams respObj = JsonConvert.DeserializeObject<StarBuildParams>(json);
ACM
  • 431
  • 4
  • 2
31

You can use reflection, something like this:

public T GetFromQueryString<T>() where T : new(){
    var obj = new T();
    var properties = typeof(T).GetProperties();
    foreach(var property in properties){
        var valueAsString = HttpContext.Current.Request.QueryString[property.PropertyName];
        var value = Parse( valueAsString, property.PropertyType);

        if(value == null)
            continue;

        property.SetValue(obj, value, null);
    }
    return obj;
 }

You'll need to implement the Parse method, just using int.Parse, decimal.Parse, DateTime.Parse, etc.

Ivo
  • 8,172
  • 5
  • 27
  • 42
16

Use this Parse method with the ivowiblo's solution (accepted answer):

public object Parse(string valueToConvert, Type dataType)
{
    TypeConverter obj = TypeDescriptor.GetConverter(dataType);
    object value = obj.ConvertFromString(null, CultureInfo.InvariantCulture,  valueToConvert);
    return value;
}
Omar Himada
  • 2,540
  • 1
  • 14
  • 31
Anupam Singh
  • 1,158
  • 13
  • 25
6

Serialize query string and deserialize to your class object

 JObject json;
 Request.RequestUri.TryReadQueryAsJson(out json);
 string sjson = JsonConvert.SerializeObject(json);
 StarBuildParams query = JsonConvert.DeserializeObject<StarBuildParams>(sjson);
Shadab Ahmed
  • 556
  • 9
  • 20
  • 1
    This deserves more upvotes. `TryReadQueryAsJson` is available in `System.Net.Http` and creates a `JObject`. No dependencies on `System.Web`. – w5l May 18 '21 at 06:36
5

You can just use .NET's HttpUtility.ParseQueryString() method:

HttpUtility.ParseQueryString("a=b&c=d") produces a NameValueCollection as such:

[0] Key = "a", Value = "b"
[1] Key = "c", Value = "d"
Ofer Zelig
  • 17,068
  • 9
  • 59
  • 93
5

You can set the properties of this object in its constructor by retrieving the relevant values from the querystring

public StarBuildParams()
{
    this.BaseNo = Int32.Parse(Request.QueryString["BaseNo"].ToString());
    this.Width = Int32.Parse(Request.QueryString["Width"].ToString());
}

and you can ensure that the object is converted to the correct querystring format by overriding the ToString method.

public override string ToString()
{
    return String.Format("BaseNo={0}&Width={1}", this.BaseNo, this.Width);
}

You'll still need to construct and call ToString in the appropriate places, but this should help.

Kirk Broadhurst
  • 27,836
  • 16
  • 104
  • 169
4

This should work so long as none of the properties match any other route parameters like controller, action, id, etc.

new RouteValueDictionary(Model)

http://msdn.microsoft.com/en-us/library/cc680272.aspx

Initializes a new instance of the RouteValueDictionary class and adds values that are based on properties from the specified object.

To parse back from the query string you can use the model class as an action parameter and let the ModelBinder do it's job.

dotjoe
  • 26,242
  • 5
  • 63
  • 77
0

Building off of Ivo and Anupam Singh's great solutions above, here is the code that I used to turn this into a base class for POST requests (in the event that you may only have the raw query string like in a Web API setup). This code works for lists of objects, but could easily be modified to parse a single object.

public class PostOBjectBase
{
        /// <summary>
        /// Returns a List of List<string> - one for each object that is going to be parsed.
        /// </summary>
        /// <param name="entryListString">Raw query string</param>
        /// <param name="firstPropertyNameOfObjectToParseTo">The first property name of the object that is sent in the list (unless otherwise specified).  Used as a key to start a new object string list.  Ex: "id", etc.</param>
        /// <returns></returns>
        public List<List<string>> GetQueryObjectsAsStringLists(string entryListString, string firstPropertyNameOfObjectToParseTo = null)
        {
            // Decode the query string (if necessary)
            string raw = System.Net.WebUtility.UrlDecode(entryListString);

            // Split the raw query string into it's data types and values
            string[] entriesRaw = raw.Split('&');

            // Set the first property name if it is not provided
            if (firstPropertyNameOfObjectToParseTo == null)
                firstPropertyNameOfObjectToParseTo = entriesRaw[0].Split("=").First();

            // Create a list from the raw query array (more easily manipulable) for me at least
            List<string> rawList = new List<string>(entriesRaw);

            // Initialize List of string lists to return - one list = one object
            List<List<string>> entriesList = new List<List<string>>();

            // Initialize List for current item to be added to in foreach loop
            bool isFirstItem = false;
            List<string> currentItem = new List<string>();

            // Iterate through each item keying off of the firstPropertyName of the object we will ultimately parse to
            foreach (string entry in rawList)
            {
                if (entry.Contains(firstPropertyNameOfObjectToParseTo + "="))
                {
                    // The first item needs to be noted in the beginning and not added to the list since it is not complete
                    if (isFirstItem == false)
                    {
                        isFirstItem = true;
                    }
                    // Finished getting the first object - we're on the next ones in the list
                    else
                    {
                        entriesList.Add(currentItem);
                        currentItem = new List<string>();
                    }
                }
                currentItem.Add(entry);
            }

            // Add the last current item since we could not in the foreach loop
            entriesList.Add(currentItem);

            return entriesList;
        }

        public T GetFromQueryString<T>(List<string> queryObject) where T : new()
        {
            var obj = new T();
            var properties = typeof(T).GetProperties();
            foreach (string entry in queryObject)
            {
                string[] entryData = entry.Split("=");
                foreach (var property in properties)
                {
                    if (entryData[0].Contains(property.Name))
                    {
                        var value = Parse(entryData[1], property.PropertyType);

                        if (value == null)
                            continue;

                        property.SetValue(obj, value, null);
                    }
                }
            }
            return obj;
        }

        public object Parse(string valueToConvert, Type dataType)
        {
            if (valueToConvert == "undefined" || valueToConvert == "null")
                valueToConvert = null;
            TypeConverter obj = TypeDescriptor.GetConverter(dataType);
            object value = obj.ConvertFromString(null, CultureInfo.InvariantCulture, valueToConvert);
            return value;
        }
}

Then you can inherit from this class in wrapper classes for POST requests and parse to whichever objects you need. In this case, the code parses a list of objects passed as a query string to a list of wrapper class objects.

For example:

public class SampleWrapperClass : PostOBjectBase
{
    public string rawQueryString { get; set; }
    public List<ObjectToParseTo> entryList
    {
        get
        {
            List<List<string>> entriesList = GetQueryObjectsAsStringLists(rawQueryString);

            List<ObjectToParseTo> entriesFormatted = new List<ObjectToParseTo>();

            foreach (List<string> currentObject in entriesList)
            {
                ObjectToParseToentryPost = GetFromQueryString<ObjectToParseTo>(currentObject);
                entriesFormatted.Add(entryPost);
            }

            return entriesFormatted;
        }
    }
}
Steven Pfeifer
  • 345
  • 4
  • 11