【问题标题】:How do I serialize an object into query-string format?如何将对象序列化为查询字符串格式?
【发布时间】:2011-10-14 11:11:57
【问题描述】:

如何将对象序列化为查询字符串格式?我似乎无法在谷歌上找到答案。谢谢。

这是我将序列化的对象作为示例。

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

【问题讨论】:

  • 为什么不创建自己的函数以这种方式进行序列化?
  • 您希望得到:Id=1&State=CA&Prefix=Mr... 类似的东西?如果是这样,我同意@James。
  • @James 哇,这是唯一的方法吗?我想在某个地方.NET 中内置了一些东西。我在想有点像 MVC 模型绑定器的逆。这个一定有方法吧?
  • 如果没有内置函数,你能告诉我怎么写吗?
  • Flurl 是一个 URL 构建器/HTTP 客户端,它广泛地将对象用于类似名称-值对的事物(查询字符串、标题、URL 编码的表单值等)。 SetQueryParams 完全符合您的要求。如果您只需要 URL 构建器而不是所有 HTTP 内容,则可以使用 here。 [免责声明:我是作者]

标签: asp.net serialization object query-string


【解决方案1】:

它对嵌套对象也很有用

public static class HttpQueryStrings
{
    public static string ToQueryString<T>(this T @this) where T : class
    {
        StringBuilder query = @this.ToQueryStringBuilder();

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

        return query.ToString();
    }

    private static StringBuilder ToQueryStringBuilder<T>(this T obj, string prefix = "") where T : class
    {
        var gatherer = new StringBuilder();

        foreach (var p in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (p.GetValue(obj, new object[0]) != null)
            {
                var value = p.GetValue(obj, new object[0]);

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

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

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

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

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


                else if (p.PropertyType.IsClass)
                    gatherer.Append(value.ToQueryStringBuilder($"{prefix}{p.Name}."));
            }
        }

        return gatherer;
    }
}

使用解决方案的示例:

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

【讨论】:

    【解决方案2】:

    使用Json.Net 会更容易,通过序列化然后反序列化为键值对。

    这是一个代码示例:

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

    【讨论】:

    • 我用它来解决 DateTime 属性的问题
    • 我喜欢这种简单。这对平面对象很好,但对嵌套对象/列表没有好处
    • 这也适用于 System.Text.Json 库,只需确保第 2 步中的 IDictionary 是 &lt;string, object&gt;,然后在第 3 步中使用 x.Value.ToString()
    【解决方案3】:

    也许这种通用方法会很有用:

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

    【讨论】:

      【解决方案4】:

      支持列表属性的简单方法:

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

      一个更快的解决方案,就像拼出代码来序列化每种类型一样快:

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

      使用任一解决方案的示例:

      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;
      

      输出:
      http://test.com/Days=Tuesday&Days=Wednesday&Time=14:30:00&Link=conferences.com%2Fapple%2Fstream%2F15
      这两种解决方案都不能处理奇异类型、索引参数或嵌套参数。

      当手动序列化比较简单时,这种 c#7/.net4.7 方法可以提供帮助:

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

      示例用法:

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

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

      【讨论】:

        【解决方案5】:

        这是我的解决方案:

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

        我将这个扩展用于我的集成测试,效果很好:)

        【讨论】:

          【解决方案6】:

          只是上述的另一种变体,但我想在我的模型类中利用现有的 DataMember 属性,因此只有我想要序列化的属性在 GET 请求的 url 中发送到服务器。

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

          【讨论】:

            【解决方案7】:
            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();
                }
            }
            

            【讨论】:

              【解决方案8】:

              在其他 cmets 的好主意的基础上,我制作了一个通用的扩展方法 .ToQueryString(),它可以用于任何对象。

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

              它也适用于具有 Array 和泛型 Lists 类型属性的对象,前提是它们只包含原语或字符串。

              试试看,欢迎cmets:Serialize object into a query string with Reflection

              【讨论】:

              • 为什么不把代码放在这里。没那么多。
              • 只是一点语法糖 if (request == null) throw new ArgumentNullException(nameof(request));
              【解决方案9】:

              根据流行的答案,我还需要更新代码以支持数组。分享实现:

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

              【讨论】:

                【解决方案10】:

                我一直在为 Windows 10 (UWP) 应用寻找解决方案。采用Dave建议的Relection方法,添加Microsoft.AspNet.WebApi.Client Nuget包后,我使用了以下代码, 它处理属性值的 URL 编码:

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

                【讨论】:

                  【解决方案11】:

                  面对类似的情况,我所做的是将 XML 序列化对象并将其作为查询字符串参数传递。 这种方法的困难在于,尽管进行了编码,但接收表单会抛出异常,说“潜在危险的请求......”。我解决的方法是加密序列化对象,然后编码以将其作为查询字符串参数传递。这反过来又使查询字符串防篡改(奖金进入 HMAC 领域)!

                  FormA XML 序列化对象 > 加密序列化字符串 > 编码 > 作为查询字符串传递给 FormB FormB 解密查询参数值(因为 request.querystring 也解码)> 使用 XmlSerializer 将生成的 XML 字符串反序列化为对象。

                  我可以根据要求分享我的 VB.NET 代码到 howIdidit-at-applecart-dot-net

                  【讨论】:

                    【解决方案12】:

                    我 99% 确定没有内置的实用程序方法。这不是一项非常常见的任务,因为 Web 服务器通常不会响应 URLEncoded 键/值字符串。

                    您对混合使用反射和 LINQ 有什么看法?这有效:

                    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());
                    

                    更新:

                    要编写一个返回任何 1-deep 对象的 QueryString 表示的方法,您可以这样做:

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

                    您也可以将其作为扩展方法而无需太多额外工作

                    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();
                    

                    【讨论】:

                    • 这很好。我试图使它成为一个接受动态参数的函数,但我假设我搞砸了动态 linq 选择语法。 public string SerializeWithDynamicLINQ(dynamic Thing) { var Properties = Thing.GetType().GetProperties().ToArray(); return "&amp;" + Properties.select("Property.Name") + "=" + HttpUtility.UrlEncode( Properties.select("Property").GetValue(Thing, null).ToString()); } 我也不知道如何在 cmets 中做代码块..
                    • @Benjamin:更新了我的答案以提供帮助。
                    • 既然你说这不是一个很常见的任务。传递大量表单值的替代方法是什么,而不必对所有 RouteValueDictionary 值进行硬编码
                    • 您可以通过将每个属性的值分配给临时变量(例如使用let value = p.GetValue(obj, null)
                    • 这仅适用于服务器上的英语文化信息。如果您在 Windows 上设置特定的日期时间格式,这将不起作用,因为您应该将 CultureInfo Invariant 设置为 ToString 参数。同样的问题是浮点/双精度中的特定小数分隔符。
                    【解决方案13】:

                    这是我写的满足你需要的东西。

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

                    【讨论】:

                    • object 也一样容易。
                    • @TheGeekYouNeed 谢谢!我会试试看。我很惊讶没有内置任何东西。这也会捕获继承的属性吗?
                    • 本杰明,我想是的 - 我不完全记得我之前写过这段代码,但记得我看到你的问题时记得。
                    猜你喜欢
                    • 2019-12-02
                    • 2012-07-18
                    • 2010-09-13
                    • 2022-11-15
                    • 2011-01-26
                    • 1970-01-01
                    相关资源
                    最近更新 更多