2015-10-13 66 views
2

假设你有下面的代码如何GET发送复杂的对象,以WEB API 2

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? ...)。

问题是它不适用于其他任何东西而不是简单的属性。 例如,要求在日期的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(); 
    } 
+1

你为什么不把它作为json发布呢? – Jehof

+2

,因为HTTP动词在REST中具有语义。 例如POST /产品做搜索很尴尬,因为你会认为这是一个创建 –

+0

序列化为JSON和URL编码? –

回答

2

“将任何物体查询字符串”似乎暗示有一个标准格式,这一点,并没有。所以你需要选择一个或者自己推出。由于伟大的图书馆的可用性,JSON似乎是显而易见的选择。

+0

这不适用于开箱即用的WEB API 2 –

+0

确实如此,但您满足处理任何*对象类型的要求。在服务器上的反序列化是单行的,或者你可以用一个自定义的'ModelBinder'完全隐藏它。 –

+0

感谢您的想法! 您的解决方案有效,但我不想牺牲URL的可读性(即{“:将被urlencoded,导致很难阅读URL) 而且我也提出解决方案,我建议不适用于任何对象 –

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

我d要感谢所有的参与者,即使这不是你通常应该做的Q & A格式:)

+0

这似乎属于“滚动你自己的”类别,并且与使用现有的序列化格式相比有很多限制,但是如果它适用于你的目的,那么很酷:) –

+0

@ToddMenier:你是绝对正确的。我的解决方案是,该网址仍然不错,与json编码的网址相比:) –

相关问题