如何将GET中的复杂对象发送到WEB API 2

时间:2015-10-13 09:33:00

标签: asp.net-web-api dotnet-httpclient

我们假设你有以下代码

public class MyClass {
   public double Latitude {get; set;}
   public double Longitude {get; set;}
}
public class Criteria
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public MyClass MyProp {get; set;}
}

[HttpGet]    
public Criteria Get([FromUri] Criteria c)
{
  return c;
}

我想知道是否有人知道可以将任何对象转换为WEB API 2控制器可以理解的查询字符串的库。

这是我喜欢的一个例子

SerializeToQueryString(new Criteria{StartDate=DateTime.Today, EndDate = DateTime.Today.AddDays(1), MyProp = new MyProp{Latitude=1, Longitude=3}}); 
=> "startDate=2015-10-13&endDate=2015-10-14&myProp.latitude=1&myProp.longitude=3"

httpClient的完整示例可能如下所示:

new HttpClient("http://localhost").GetAsync("/tmp?"+SerializeToQueryString(new Criteria{StartDate=DateTime.Today, EndDate = DateTime.Today.AddDays(1), MyProp = new MyProp{Latitude=1, Longitude=3}})).Result;

目前,我使用的是一个版本(取自我再也找不到的问题,也许是How do I serialize an object into query-string format? ...)。

问题在于除了简单的属性之外,它不能用于其他任何事情。 例如,在Date上调用ToString将不会提供WEB API 2控制器可解析的内容......

    private string SerializeToQueryString<T>(T aObject)
    {
        var query = HttpUtility.ParseQueryString(string.Empty);
        var fields = typeof(T).GetProperties();
        foreach (var field in fields)
        {
            string key = field.Name;
            var value = field.GetValue(aObject);
            if (value != null)
                query[key] = value.ToString();
        }
        return query.ToString();
    }

2 个答案:

答案 0 :(得分:2)

&#34;将任何对象转换为查询字符串&#34;似乎暗示这是一种标准格式,而且根本就没有。所以你需要选择一个或自己动手。由于可用的库很好,JSON似乎是显而易见的选择。

答案 1 :(得分:0)

由于以前似乎没有人处理过这个问题,这里是我在项目中使用的解决方案:

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Web;

namespace App
{
    public class QueryStringSerializer
    {
        public static string SerializeToQueryString(object aObject)
        {
            return SerializeToQueryString(aObject, "").ToString();
        }

        private static NameValueCollection SerializeToQueryString(object aObject, string prefix)
        {
            //!\ doing this to get back a HttpValueCollection which is an internal class
            //we want a HttpValueCollection because toString on this class is what we want in the public method
            //cf http://stackoverflow.com/a/17096289/1545567
            var query = HttpUtility.ParseQueryString(String.Empty); 
            var fields = aObject.GetType().GetProperties();
            foreach (var field in fields)
            {
                string key = string.IsNullOrEmpty(prefix) ? field.Name : prefix + "." + field.Name;
                var value = field.GetValue(aObject);
                if (value != null)
                {
                    var propertyType = GetUnderlyingPropertyType(field.PropertyType);
                    if (IsSupportedType(propertyType))
                    {
                        query.Add(key, ToString(value)); 
                    }
                    else if (value is IEnumerable)
                    {
                        var enumerableValue = (IEnumerable) value;
                        foreach (var enumerableValueElement in enumerableValue)
                        {
                            if (IsSupportedType(GetUnderlyingPropertyType(enumerableValueElement.GetType())))
                            {
                                query.Add(key, ToString(enumerableValueElement));
                            } 
                            else
                            {
                                //it seems that WEB API 2 Controllers are unable to deserialize collections of complex objects...
                                throw new Exception("can not use IEnumerable<T> where T is a class because it is not understood server side");  
                            }                            
                        }
                    }
                    else
                    {
                        var subquery = SerializeToQueryString(value, key);
                        query.Add(subquery);
                    }

                }
            }
            return query;
        }

        private static Type GetUnderlyingPropertyType(Type propType)
        {
            var nullablePropertyType = Nullable.GetUnderlyingType(propType);
            return nullablePropertyType ?? propType;
        }

        private static bool IsSupportedType(Type propertyType)
        {
            return SUPPORTED_TYPES.Contains(propertyType) || propertyType.IsEnum;
        }

        private static readonly Type[] SUPPORTED_TYPES = new[]
        {
            typeof(DateTime),
            typeof(string),
            typeof(int),
            typeof(long),
            typeof(float),
            typeof(double)
        };

        private static string ToString(object value)
        {
            if (value is DateTime)
            {
                var dateValue = (DateTime) value;
                if (dateValue.Hour == 0 && dateValue.Minute == 0 && dateValue.Second == 0)
                {
                    return dateValue.ToString("yyyy-MM-dd");
                }
                else
                {
                    return dateValue.ToString("yyyy-MM-dd HH:mm:ss");
                }
            }
            else if (value is float)
            {
                return ((float) value).ToString(CultureInfo.InvariantCulture);
            }
            else if (value is double)
            {
                return ((double)value).ToString(CultureInfo.InvariantCulture);
            }
            else /*int, long, string, ENUM*/
            {
                return value.ToString();
            }
        }
    }
}

以下是演示的单元测试:

using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Framework.WebApi.Core.Tests
{
    [TestClass]
    public class QueryStringSerializerTest
    {
        public class EasyObject
        {
            public string MyString { get; set; }
            public int? MyInt { get; set; }
            public long? MyLong { get; set; }
            public float? MyFloat { get; set; }
            public double? MyDouble { get; set; }            
        }

        [TestMethod]
        public void TestEasyObject()
        {
            var queryString = QueryStringSerializer.SerializeToQueryString(new EasyObject(){MyString = "string", MyInt = 1, MyLong = 1L, MyFloat = 1.5F, MyDouble = 1.4});
            Assert.IsTrue(queryString.Contains("MyString=string"));
            Assert.IsTrue(queryString.Contains("MyInt=1"));
            Assert.IsTrue(queryString.Contains("MyLong=1"));
            Assert.IsTrue(queryString.Contains("MyFloat=1.5"));
            Assert.IsTrue(queryString.Contains("MyDouble=1.4"));
        }

        [TestMethod]
        public void TestEasyObjectNullable()
        {
            var queryString = QueryStringSerializer.SerializeToQueryString(new EasyObject() {  });
            Assert.IsTrue(queryString == "");
        }

        [TestMethod]
        public void TestUrlEncoding()
        {
            var queryString = QueryStringSerializer.SerializeToQueryString(new EasyObject() { MyString = "&=/;+" });
            Assert.IsTrue(queryString.Contains("MyString=%26%3d%2f%3b%2b"));            
        }

        public class DateObject
        {
            public DateTime MyDate { get; set; }            
        }

        [TestMethod]
        public void TestDate()
        {
            var d = DateTime.ParseExact("2010-10-13", "yyyy-MM-dd", CultureInfo.InvariantCulture);
            var queryString = QueryStringSerializer.SerializeToQueryString(new DateObject() { MyDate = d });
            Assert.IsTrue(queryString.Contains("MyDate=2010-10-13"));
        }

        [TestMethod]
        public void TestDateTime()
        {
            var d = DateTime.ParseExact("2010-10-13 20:00", "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
            var queryString = QueryStringSerializer.SerializeToQueryString(new DateObject() { MyDate = d });
            Assert.IsTrue(queryString.Contains("MyDate=2010-10-13+20%3a00%3a00"));
        }


        public class InnerComplexObject
        {
            public double Lat { get; set; }
            public double Lon { get; set; }
        }

        public class ComplexObject
        {
            public InnerComplexObject Inner { get; set; }    
        }

        [TestMethod]
        public void TestComplexObject()
        {
            var queryString = QueryStringSerializer.SerializeToQueryString(new ComplexObject() { Inner = new InnerComplexObject() {Lat = 50, Lon = 2} });
            Assert.IsTrue(queryString.Contains("Inner.Lat=50"));
            Assert.IsTrue(queryString.Contains("Inner.Lon=2"));
        }

        public class EnumerableObject
        {
            public IEnumerable<int> InnerInts { get; set; }
        }

        [TestMethod]
        public void TestEnumerableObject()
        {
            var queryString = QueryStringSerializer.SerializeToQueryString(new EnumerableObject() { 
                InnerInts = new[] { 1,2 }
            });
            Assert.IsTrue(queryString.Contains("InnerInts=1"));
            Assert.IsTrue(queryString.Contains("InnerInts=2"));
        }

        public class ComplexEnumerableObject
        {
            public IEnumerable<InnerComplexObject> Inners { get; set; }
        }

        [TestMethod]
        public void TestComplexEnumerableObject()
        {
            try
            {
                QueryStringSerializer.SerializeToQueryString(new ComplexEnumerableObject()
                {
                    Inners = new[]
                    {
                        new InnerComplexObject() {Lat = 50, Lon = 2},
                        new InnerComplexObject() {Lat = 51, Lon = 3},
                    }
                });
                Assert.Fail("we should refuse something that will not be understand by the server");
            }
            catch (Exception e)
            {
                Assert.AreEqual("can not use IEnumerable<T> where T is a class because it is not understood server side", e.Message);
            }
        }

        public enum TheEnum : int
        {
            One = 1,
            Two = 2
        }


        public class EnumObject
        {
            public TheEnum? MyEnum { get; set; }
        }

        [TestMethod]
        public void TestEnum()
        {
            var queryString = QueryStringSerializer.SerializeToQueryString(new EnumObject() { MyEnum = TheEnum.Two});
            Assert.IsTrue(queryString.Contains("MyEnum=Two"));
        }
    }
}

我要感谢所有参与者,即使这不是您通常应该以Q&amp; A格式进行的事情:)