问题描述:

I've written a little piece of code which performs a simple HTTP post against an external API which my MVC app connects to.

The code works, but I'm tired of building a string from my object in order to post it. What I'd like to do is re-use the Model part of the MVC pattern, and POST that. Here's what I'd like to be able to do.

MyModel model = new MyModel();

model.Name = "joe";

model.Age = 33;

model.Hobbies = new List<Hobby>();

model.Hobbies.Add(new Hobby{ hobby = "fargling", level = "expert"});

model.Hobbies.Add(new Hobby{ hobby = "dibbling", level = "novice"});

HttpHelper.doPOST("http://www.myservice.com/api", model);

doPost() would serialize the model into a string that looks like this

Name=Joe&Age=33&Hobbies[0].hobby=fargling&Hobbies[0].level=expert&Hobbies[1].hobby=dibbling&Hobbies[1].level=novice

For any individual cases I can write code to build a string like this, but is there a .NET method that already does this?

网友答案:

I'm not aware of anything in C# to do this, but you could use jQuery to do this client-side.

var data = @Html.Raw(Json.Encode(Model));
var query = $.param(data);

UPDATE

I didn't notice that you just decided to use JSON. Of course, that's the easiest and best option if the API endpoint supports it (are there really any that don't, though?). Nevertheless, I was intrigued by how to actually serialize an object into x-www-form-urlecoded in C#, and was more than a little surprised that there's no built in way that I could find. Even with the FormUrlEncodedMediaTypeFormatter in Web API, they side-stepped the issue by making it a read-only formatter (i.e. it can read x-www-form-urlencoded into a object, but not vice-versa). So, I wrote this kind of quick and dirty method. (Note: It uses reflection, so insert flame here, but I'm not sure how else you would achieve something like this.)

public void BuildFormUrlEncodedDataString(Type type, object obj, StringBuilder builder, string prefix = "")
{

    var props = type.GetProperties();
    foreach (var prop in props)
    {
        if (prop.CanRead)
        {
            if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) && prop.PropertyType.IsGenericType)
            {
                IEnumerable list = (IEnumerable)prop.GetValue(obj);
                if (list != null)
                {
                    var i = 0;
                    foreach (var item in list)
                    {
                        if (item != null)
                        {
                            prefix += string.Format("{0}[{1}].", HttpUtility.UrlEncode(prop.Name), i);
                            BuildFormUrlEncodedDataString(item.GetType(), item, builder, prefix);
                            i++;
                        }
                    }
                }
            }
            else  if (prop.PropertyType.IsPrimitive || prop.PropertyType.Equals(typeof(String)))
            {
                var item = prop.GetValue(obj);
                if (item != null)
                {
                    builder.AppendFormat("{0}{1}{2}={3}",
                        builder.Length == 0 ? string.Empty : "&",
                        prefix,
                        prop.Name,
                        HttpUtility.UrlEncode(item.ToString())
                    );
                }
            }
            else
            {
                var item = prop.GetValue(obj);
                if (item != null)
                {
                    prefix += string.Format("{0}.", HttpUtility.UrlEncode(prop.Name));
                    BuildFormUrlEncodedDataString(item.GetType(), item, builder, prefix);
                }
            }
        }
    }
}

And you would use it like so:

var builder = new StringBuilder();
BuildFormUrlEncodedDataString(myObject.GetType(), myObject, builder)
var queryString = builder.ToString();

It's more an intellectual exercise at this point, since you no longer need it, but it was a fun diversion. Maybe it will come in handy to someone else.

网友答案:

I've found a workaround to this, which it turns out is a far better way of doing things anyway.

Instead of POSTing a querystring, I can simply serialize the object to JSON and then POST the JSON to the API instead. The API I'm using also accepts JSON so this saves me a lot of hassle.

For anyone who's interested in a solution to the above, I found some results under this question - How do I serialize an object into query-string format (which is also a far better way of phrasing what I was trying to say)

相关阅读:
Top